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