mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Source of it: https://github.com/dotnet/arcade/tree/main/src/Microsoft.DotNet.XUnitExtensions.Shared/Attributespull/2854/head
10 changed files with 307 additions and 36 deletions
@ -0,0 +1,13 @@ |
|||
// 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 Microsoft.DotNet.XUnitExtensions |
|||
{ |
|||
internal class ConditionalDiscovererException : Exception |
|||
{ |
|||
public ConditionalDiscovererException(string message) |
|||
: base(message) { } |
|||
} |
|||
} |
|||
@ -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; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using Microsoft.DotNet.XUnitExtensions; |
|||
using Xunit.Sdk; |
|||
|
|||
namespace Xunit |
|||
{ |
|||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] |
|||
public sealed class ConditionalFactAttribute : FactAttribute |
|||
{ |
|||
[DynamicallyAccessedMembers(StaticReflectionConstants.ConditionalMemberKinds)] |
|||
public Type CalleeType { get; private set; } |
|||
|
|||
public string[] ConditionMemberNames { get; private set; } |
|||
|
|||
public ConditionalFactAttribute( |
|||
[DynamicallyAccessedMembers(StaticReflectionConstants.ConditionalMemberKinds)] |
|||
Type calleeType, |
|||
params string[] conditionMemberNames) |
|||
{ |
|||
CalleeType = calleeType; |
|||
ConditionMemberNames = conditionMemberNames; |
|||
string skipReason = ConditionalTestDiscoverer.EvaluateSkipConditions(calleeType, conditionMemberNames); |
|||
if (skipReason != null) |
|||
Skip = skipReason; |
|||
} |
|||
|
|||
[Obsolete( |
|||
"Use the overload that takes a Type parameter: ConditionalFact(typeof(MyClass), nameof(MyCondition)).")] |
|||
public ConditionalFactAttribute(params string[] conditionMemberNames) |
|||
{ |
|||
ConditionMemberNames = conditionMemberNames; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,146 @@ |
|||
// 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.Diagnostics.CodeAnalysis; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
using Xunit.Sdk; |
|||
|
|||
namespace Microsoft.DotNet.XUnitExtensions |
|||
{ |
|||
// Internal helper class for code common to conditional test discovery through
|
|||
// [ConditionalFact] and [ConditionalTheory]
|
|||
internal static class ConditionalTestDiscoverer |
|||
{ |
|||
/// <summary>
|
|||
/// Evaluates skip conditions given an explicit callee type and condition member names.
|
|||
/// Used by attribute constructors in xunit v3 where discoverers are not needed.
|
|||
/// </summary>
|
|||
internal static string EvaluateSkipConditions( |
|||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type calleeType, |
|||
string[] conditionMemberNames |
|||
) |
|||
{ |
|||
if ( |
|||
calleeType == null |
|||
|| conditionMemberNames == null |
|||
|| conditionMemberNames.Length == 0 |
|||
) |
|||
return null; |
|||
|
|||
List<string> falseConditions = new(conditionMemberNames.Length); |
|||
foreach (string entry in conditionMemberNames) |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(entry)) |
|||
continue; |
|||
|
|||
Func<bool> conditionFunc = LookupConditionalMember(calleeType, entry); |
|||
if (conditionFunc == null) |
|||
throw new ConditionalDiscovererException( |
|||
GetFailedLookupString(entry, calleeType) |
|||
); |
|||
|
|||
try |
|||
{ |
|||
if (!conditionFunc()) |
|||
falseConditions.Add(entry); |
|||
} |
|||
catch (Exception exc) |
|||
{ |
|||
falseConditions.Add($"{entry} ({exc.GetType().Name})"); |
|||
} |
|||
} |
|||
|
|||
return falseConditions.Count > 0 |
|||
? string.Format( |
|||
"Condition(s) not met: \"{0}\"", |
|||
string.Join("\", \"", falseConditions) |
|||
) |
|||
: null; |
|||
} |
|||
|
|||
internal static string GetFailedLookupString(string name, Type type) |
|||
{ |
|||
return $"An appropriate member '{name}' could not be found. " |
|||
+ $"The conditional method needs to be a static method, property, or field on the type {type} or any ancestor, " |
|||
+ "of any visibility, accepting zero arguments, and having a return type of Boolean."; |
|||
} |
|||
|
|||
internal static Func<bool> LookupConditionalMember(Type t, string name) |
|||
{ |
|||
if (t == null || name == null) |
|||
return null; |
|||
|
|||
TypeInfo ti = t.GetTypeInfo(); |
|||
|
|||
MethodInfo mi = ti.GetDeclaredMethod(name); |
|||
if ( |
|||
mi != null |
|||
&& mi.IsStatic |
|||
&& mi.GetParameters().Length == 0 |
|||
&& mi.ReturnType == typeof(bool) |
|||
) |
|||
return () => (bool)mi.Invoke(null, null); |
|||
|
|||
PropertyInfo pi = ti.GetDeclaredProperty(name); |
|||
if ( |
|||
pi != null |
|||
&& pi.PropertyType == typeof(bool) |
|||
&& pi.GetMethod != null |
|||
&& pi.GetMethod.IsStatic |
|||
&& pi.GetMethod.GetParameters().Length == 0 |
|||
) |
|||
return () => (bool)pi.GetValue(null); |
|||
|
|||
FieldInfo fi = ti.GetDeclaredField(name); |
|||
if (fi != null && fi.FieldType == typeof(bool) && fi.IsStatic) |
|||
return () => (bool)fi.GetValue(null); |
|||
|
|||
return LookupConditionalMember(ti.BaseType, name); |
|||
} |
|||
|
|||
internal static bool CheckInputToSkipExecution( |
|||
object[] conditionArguments, |
|||
ref Type calleeType, |
|||
ref string[] conditionMemberNames, |
|||
object testMethod = null |
|||
) |
|||
{ |
|||
// A null or empty list of conditionArguments is treated as "no conditions".
|
|||
// and the test cases will be executed.
|
|||
// Example: [ConditionalClass()]
|
|||
if (conditionArguments == null || conditionArguments.Length == 0) |
|||
return true; |
|||
|
|||
calleeType = conditionArguments[0] as Type; |
|||
if (calleeType != null) |
|||
{ |
|||
if (conditionArguments.Length < 2) |
|||
{ |
|||
// [ConditionalFact(typeof(x))] no provided methods.
|
|||
return true; |
|||
} |
|||
|
|||
// [ConditionalFact(typeof(x), "MethodName")]
|
|||
conditionMemberNames = conditionArguments[1] as string[]; |
|||
} |
|||
else |
|||
{ |
|||
// For [ConditionalClass], unable to get the Type info. All test cases will be executed.
|
|||
if (testMethod == null) |
|||
return true; |
|||
|
|||
// [ConditionalFact("MethodName")]
|
|||
conditionMemberNames = conditionArguments[0] as string[]; |
|||
} |
|||
|
|||
// [ConditionalFact((string[]) null)]
|
|||
if (conditionMemberNames == null || conditionMemberNames.Count() == 0) |
|||
return true; |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
// 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.Diagnostics.CodeAnalysis; |
|||
using Microsoft.DotNet.XUnitExtensions; |
|||
using Xunit.Sdk; |
|||
|
|||
namespace Xunit |
|||
{ |
|||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] |
|||
public sealed class ConditionalTheoryAttribute : TheoryAttribute |
|||
{ |
|||
[DynamicallyAccessedMembers(StaticReflectionConstants.ConditionalMemberKinds)] |
|||
public Type CalleeType { get; private set; } |
|||
public string[] ConditionMemberNames { get; private set; } |
|||
|
|||
public ConditionalTheoryAttribute( |
|||
[DynamicallyAccessedMembers(StaticReflectionConstants.ConditionalMemberKinds)] |
|||
Type calleeType, |
|||
params string[] conditionMemberNames |
|||
) |
|||
{ |
|||
CalleeType = calleeType; |
|||
ConditionMemberNames = conditionMemberNames; |
|||
string skipReason = ConditionalTestDiscoverer.EvaluateSkipConditions( |
|||
calleeType, |
|||
conditionMemberNames |
|||
); |
|||
if (skipReason != null) |
|||
Skip = skipReason; |
|||
} |
|||
|
|||
[Obsolete( |
|||
"Use the overload that takes a Type parameter: ConditionalTheory(typeof(MyClass), nameof(MyCondition))." |
|||
)] |
|||
public ConditionalTheoryAttribute(params string[] conditionMemberNames) |
|||
{ |
|||
ConditionMemberNames = conditionMemberNames; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
// Licensed to the .NET Foundation under one or more agreements.
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
|||
|
|||
using System.Diagnostics.CodeAnalysis; |
|||
|
|||
namespace Xunit |
|||
{ |
|||
internal static class StaticReflectionConstants |
|||
{ |
|||
// ConditionalTestDiscoverer looks at all fields/methods/properties, recursively.
|
|||
internal const DynamicallyAccessedMemberTypes ConditionalMemberKinds = |
|||
DynamicallyAccessedMemberTypes.AllMethods |
|||
| DynamicallyAccessedMemberTypes.AllFields |
|||
| DynamicallyAccessedMemberTypes.AllProperties; |
|||
} |
|||
} |
|||
|
|||
namespace System.Diagnostics.CodeAnalysis |
|||
{ |
|||
// This is a copy of the attribute from CoreLib. The attribute shipped in .NET 5.
|
|||
// The tooling looks for the attribute by name, so we can define a local copy.
|
|||
// This copy can be deleted once we stop supporting anything below .NET 5.
|
|||
internal sealed class DynamicallyAccessedMembersAttribute : Attribute |
|||
{ |
|||
public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) => |
|||
MemberTypes = memberTypes; |
|||
|
|||
public DynamicallyAccessedMemberTypes MemberTypes { get; } |
|||
} |
|||
|
|||
internal enum DynamicallyAccessedMemberTypes |
|||
{ |
|||
None = 0, |
|||
PublicParameterlessConstructor = 0x0001, |
|||
PublicConstructors = 0x0002 | PublicParameterlessConstructor, |
|||
NonPublicConstructors = 0x0004, |
|||
PublicMethods = 0x0008, |
|||
NonPublicMethods = 0x0010, |
|||
PublicFields = 0x0020, |
|||
NonPublicFields = 0x0040, |
|||
PublicNestedTypes = 0x0080, |
|||
NonPublicNestedTypes = 0x0100, |
|||
PublicProperties = 0x0200, |
|||
NonPublicProperties = 0x0400, |
|||
PublicEvents = 0x0800, |
|||
NonPublicEvents = 0x1000, |
|||
Interfaces = 0x2000, |
|||
NonPublicConstructorsWithInherited = NonPublicConstructors | 0x4000, |
|||
NonPublicMethodsWithInherited = NonPublicMethods | 0x8000, |
|||
NonPublicFieldsWithInherited = NonPublicFields | 0x10000, |
|||
NonPublicNestedTypesWithInherited = NonPublicNestedTypes | 0x20000, |
|||
NonPublicPropertiesWithInherited = NonPublicProperties | 0x40000, |
|||
NonPublicEventsWithInherited = NonPublicEvents | 0x80000, |
|||
PublicConstructorsWithInherited = PublicConstructors | 0x100000, |
|||
PublicNestedTypesWithInherited = PublicNestedTypes | 0x200000, |
|||
AllConstructors = PublicConstructorsWithInherited | NonPublicConstructorsWithInherited, |
|||
AllMethods = PublicMethods | NonPublicMethodsWithInherited, |
|||
AllFields = PublicFields | NonPublicFieldsWithInherited, |
|||
AllNestedTypes = PublicNestedTypesWithInherited | NonPublicNestedTypesWithInherited, |
|||
AllProperties = PublicProperties | NonPublicPropertiesWithInherited, |
|||
AllEvents = PublicEvents | NonPublicEventsWithInherited, |
|||
All = ~None, |
|||
} |
|||
} |
|||
Loading…
Reference in new issue