-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
src/tests tree test xunit-based source generated runner #60846
Changes from 9 commits
1bbfc9c
6523a11
6143a48
9b9ae95
54e93eb
14ca067
149ffe2
23b7975
623cc48
a20864e
4a7ebe9
f5009c8
59960aa
191ac42
a2bdb78
e597fec
e783f9e
c978193
270144b
4c73d6f
e81deda
4f9dcbb
d5961f6
4d412d0
377b8a0
835f56d
c84c5d1
b7fb243
d49c1f4
1b19bed
040877c
d736995
a77d13c
04650fd
8ef73bd
5881fa1
eccf29a
ccd08a2
db68ce4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>net6.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="$(RepoRoot)/src/tests/Common/XUnitWrapperGenerator/XUnitWrapperGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> | ||
</ItemGroup> | ||
|
||
<Import Project="$(RepoRoot)/src/tests/Common/XUnitWrapperGenerator/XUnitWrapperGenerator.props" /> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="$(RepoRoot)\src\tests\JIT\IL_Conformance\Old\directed\AutoInit.ilproj" Aliases="autoinit" /> | ||
</ItemGroup> | ||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using Microsoft.CodeAnalysis; | ||
|
||
namespace XUnitWrapperGenerator; | ||
|
||
interface ITestInfo | ||
{ | ||
string ExecutionStatement { get; } | ||
} | ||
|
||
sealed class BasicTestMethod : ITestInfo | ||
{ | ||
public BasicTestMethod(IMethodSymbol method, string externAlias, ImmutableArray<string> arguments = default) | ||
{ | ||
var args = arguments.IsDefaultOrEmpty ? "" : string.Join(", ", arguments); | ||
string containingType = method.ContainingType.ToDisplayString(XUnitWrapperGenerator.FullyQualifiedWithoutGlobalNamespace); | ||
if (method.IsStatic) | ||
{ | ||
ExecutionStatement = $"{externAlias}::{containingType}.{method.Name}({args});"; | ||
} | ||
else | ||
{ | ||
ExecutionStatement = $"using ({externAlias}::{containingType} obj = new()) obj.{method.Name}({args});"; | ||
} | ||
} | ||
|
||
public string ExecutionStatement { get; } | ||
|
||
public override bool Equals(object obj) | ||
{ | ||
return obj is BasicTestMethod other && ExecutionStatement == other.ExecutionStatement; | ||
} | ||
} | ||
|
||
sealed class ConditionalTest : ITestInfo | ||
{ | ||
public ConditionalTest(ITestInfo innerTest, string condition) | ||
{ | ||
ExecutionStatement = $"if ({condition}) {{ {innerTest.ExecutionStatement} }}"; | ||
} | ||
|
||
public string ExecutionStatement { get; } | ||
|
||
public override bool Equals(object obj) | ||
{ | ||
return obj is ConditionalTest other && ExecutionStatement == other.ExecutionStatement; | ||
} | ||
} | ||
|
||
sealed class PlatformSpecificTest : ITestInfo | ||
{ | ||
public PlatformSpecificTest(ITestInfo innerTest, Xunit.TestPlatforms platform) | ||
{ | ||
List<string> platformCheckConditions = new(); | ||
if (platform.HasFlag(Xunit.TestPlatforms.Windows)) | ||
{ | ||
platformCheckConditions.Add("global::System.OperatingSystem.IsWindows()"); | ||
} | ||
if (platform.HasFlag(Xunit.TestPlatforms.Linux)) | ||
{ | ||
platformCheckConditions.Add("global::System.OperatingSystem.IsLinux()"); | ||
} | ||
if (platform.HasFlag(Xunit.TestPlatforms.OSX)) | ||
{ | ||
platformCheckConditions.Add("global::System.OperatingSystem.IsMacOS()"); | ||
} | ||
if (platform.HasFlag(Xunit.TestPlatforms.illumos)) | ||
{ | ||
platformCheckConditions.Add(@"global::System.OperatingSystem.IsOSPlatform(""illumos"")"); | ||
} | ||
if (platform.HasFlag(Xunit.TestPlatforms.Solaris)) | ||
{ | ||
platformCheckConditions.Add(@"global::System.OperatingSystem.IsOSPlatform(""Solaris"")"); | ||
} | ||
if (platform.HasFlag(Xunit.TestPlatforms.Android)) | ||
{ | ||
platformCheckConditions.Add("global::System.OperatingSystem.IsAndroid()"); | ||
} | ||
if (platform.HasFlag(Xunit.TestPlatforms.iOS)) | ||
{ | ||
platformCheckConditions.Add("global::System.OperatingSystem.IsIOS()"); | ||
jkoritzinsky marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
if (platform.HasFlag(Xunit.TestPlatforms.tvOS)) | ||
{ | ||
platformCheckConditions.Add("global::System.OperatingSystem.IsAndroid()"); | ||
jkoritzinsky marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
if (platform.HasFlag(Xunit.TestPlatforms.MacCatalyst)) | ||
{ | ||
platformCheckConditions.Add(@"global::System.OperatingSystem.IsOSPlatform(""maccatalyst"")"); | ||
jkoritzinsky marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
if (platform.HasFlag(Xunit.TestPlatforms.Browser)) | ||
{ | ||
platformCheckConditions.Add(@"global::System.OperatingSystem.IsOSPlatform(""browser"")"); | ||
jkoritzinsky marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
if (platform.HasFlag(Xunit.TestPlatforms.FreeBSD)) | ||
{ | ||
platformCheckConditions.Add(@"global::System.OperatingSystem.IsFreeBSD()"); | ||
} | ||
if (platform.HasFlag(Xunit.TestPlatforms.NetBSD)) | ||
{ | ||
platformCheckConditions.Add(@"global::System.OperatingSystem.IsOSPlatform(""NetBSD"")"); | ||
safern marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
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; | ||
} | ||
} | ||
|
||
sealed class MemberDataTest : ITestInfo | ||
{ | ||
public MemberDataTest(ISymbol referencedMember, ITestInfo innerTest, string externAlias, string argumentLoopVarIdentifier) | ||
{ | ||
string containingType = referencedMember.ContainingType.ToDisplayString(XUnitWrapperGenerator.FullyQualifiedWithoutGlobalNamespace); | ||
string 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}) | ||
{{ | ||
{innerTest.ExecutionStatement} | ||
}} | ||
"; | ||
} | ||
|
||
public string ExecutionStatement { get; } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Text; | ||
|
||
namespace XUnitWrapperGenerator | ||
{ | ||
internal class ImmutableDictionaryValueComparer<TKey, TValue> : IEqualityComparer<ImmutableDictionary<TKey, TValue>> | ||
where TKey : notnull | ||
{ | ||
private readonly IEqualityComparer<TValue> _valueComparer; | ||
|
||
public ImmutableDictionaryValueComparer(IEqualityComparer<TValue> valueComparer) | ||
{ | ||
_valueComparer = valueComparer; | ||
} | ||
|
||
public bool Equals(ImmutableDictionary<TKey, TValue> x, ImmutableDictionary<TKey, TValue> y) | ||
{ | ||
if (x.Count != y.Count) | ||
{ | ||
return false; | ||
} | ||
|
||
foreach (var pair in x) | ||
{ | ||
if (!y.TryGetValue(pair.Key, out TValue? value) || !_valueComparer.Equals(value, pair.Value)) | ||
{ | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
public int GetHashCode(ImmutableDictionary<TKey, TValue> obj) => throw new NotImplementedException(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
|
||
namespace Xunit | ||
{ | ||
[Flags] | ||
public enum RuntimeConfiguration | ||
{ | ||
Any = ~0, | ||
Checked = 1, | ||
Debug = 1 << 1, | ||
Release = 1 << 2 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
|
||
namespace Xunit | ||
{ | ||
[Flags] | ||
public enum RuntimeTestModes | ||
{ | ||
// Disable always when using coreclr runtime. | ||
Any = ~0, | ||
|
||
// We're running regular tests with no runtime stress modes | ||
RegularRun = 1, | ||
|
||
// JitStress, JitStressRegs, JitMinOpts and TailcallStress enable | ||
// various modes in the JIT that cause us to exercise more code paths, | ||
// and generate different kinds of code | ||
JitStress = 1 << 1, // COMPlus_JitStress is set. | ||
JitStressRegs = 1 << 2, // COMPlus_JitStressRegs is set. | ||
JitMinOpts = 1 << 3, // COMPlus_JITMinOpts is set. | ||
TailcallStress = 1 << 4, // COMPlus_TailcallStress is set. | ||
|
||
// ZapDisable says to not use NGEN or ReadyToRun images. | ||
// This means we JIT everything. | ||
ZapDisable = 1 << 5, // COMPlus_ZapDisable is set. | ||
|
||
// GCStress3 forces a GC at various locations, typically transitions | ||
// to/from the VM from managed code. | ||
GCStress3 = 1 << 6, // COMPlus_GCStress includes mode 0x3. | ||
|
||
// GCStressC forces a GC at every JIT-generated code instruction, | ||
// including in NGEN/ReadyToRun code. | ||
GCStressC = 1 << 7, // COMPlus_GCStress includes mode 0xC. | ||
AnyGCStress = GCStress3 | GCStressC // Disable when any GCStress is exercised. | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
|
||
namespace Xunit | ||
{ | ||
[Flags] | ||
public enum TestPlatforms | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we reflect on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can, but using the Roslyn symbol APIs to reflect like that is not fast. I think eventually making this generator live in dotnet/arcade might be a better solution. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In future versions of the generator (v1 milestone of the new test infra), we need to support spitting out an XUnit-style results file as well as basic test filtering, so living in arcade would make more sense then. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should be careful not to regress managed build time too significantly. While ultimately we'll optimize it by compiling tests as larger apps, it remains to be seen whether compiling tests in merged form makes sense for all pipelines - some of the GC stress pipelines come to mind where some tests are timing out already today even without merging - so that the "standalone" mode is likely to stay as a valid way of running tests at least for niche scenarios. If we pay the reflection startup cost just once per Roslyn generation of all the wrappers, it's probably just fine but paying the cost for every single test may be significant. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't like caching it like that due to the fact that it's technically not correct and can break some assumptions that Roslyn has about the lifetime of a generator (as Roslyn can throw them away or reuse them in many different ways including in compiler server scenarios) |
||
{ | ||
Windows = 1, | ||
Linux = 2, | ||
OSX = 4, | ||
FreeBSD = 8, | ||
NetBSD = 16, | ||
illumos= 32, | ||
Solaris = 64, | ||
iOS = 128, | ||
tvOS = 256, | ||
Android = 512, | ||
Browser = 1024, | ||
MacCatalyst = 2048, | ||
AnyUnix = FreeBSD | Linux | NetBSD | OSX | illumos | Solaris | iOS | tvOS | MacCatalyst | Android | Browser, | ||
Any = ~0 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
|
||
namespace Xunit | ||
safern marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
[Flags] | ||
public enum TestRuntimes | ||
{ | ||
CoreCLR = 1, | ||
Mono = 2, | ||
Any = ~0 | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just bite the bullet and rename the conflicting types instead? I think it will save us a lot of troubles in the long run. It is super confusing to debug something when the types with same name are defined in different assemblies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had support for this functionality in the ILPROJ analyzer. I have temporarily removed it because it made the initial testing simpler but I can easily put it back. The biggest batch I'm aware of is the 1501 type generator tests that all use the class name
Framework
, I think the easiest approach here is to rename the class name to the test name (e.g. Generated1379) as in case of merging we can't tolerate duplicates in the simple test output dll names anyway.