Skip to content

Commit

Permalink
Support required property on test class.
Browse files Browse the repository at this point in the history
  • Loading branch information
pengweiqhca committed Oct 30, 2024
1 parent 93f998b commit 51348a3
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 4 deletions.
56 changes: 56 additions & 0 deletions src/PackAllProject.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
function FindMSBuild () {
if ($null -eq $env:OS) {
try {
return (Get-Command msbuild).Source;
}
catch {
return $null;
}
}

foreach ($program in ("C:\Program Files", "D:\Program Files")) {
foreach ($vs in (Get-ChildItem ($program + "\Microsoft Visual Studio"))) {
foreach ($vsv in (Get-ChildItem $vs.FullName)) {
if (Test-Path ($vsv.FullName + "\MSBuild\Current\Bin\amd64\MSBuild.exe")) {
return $vsv.FullName + "\MSBuild\Current\Bin\amd64\MSBuild.exe";
}
}
}
}

return $null;
}

if (-not(Test-Path .packages)) {
mkdir .packages
}

foreach ($csproj in (Get-ChildItem -r -filter *.csproj)) {
$dir = "$([System.IO.Path]::GetDirectoryName($csproj.FullName))\bin\Release";
if (Test-Path $dir) {
Remove-Item -Recurse -Force $dir;
}
}

$MSBuild = FindMSBuild
if ($null -eq $MSBuild) {
dotnet build -c Release /p:IsPacking=true ..
}
else {
& "$MSBuild" /r /m /v:m /p:Configuration=Release /p:IsPacking=true ..
}

foreach ($csproj in (Get-ChildItem -r -filter *.csproj)) {
$dir = "$([System.IO.Path]::GetDirectoryName($csproj.FullName))\bin\Release";

if (Test-Path $dir) {
$nupkg = Get-ChildItem "$([System.IO.Path]::GetDirectoryName($csproj.FullName))\bin\Release" |
Where-Object { $_.Name.Endswith(".nupkg") } |
Sort-Object -Property LastWriteTime -Descending |
Select-Object -First 1;

if ($null -ne $nupkg) {
Copy-Item $nupkg.VersionInfo.FIleName (".packages\" + $nupkg.Name) -Force
}
}
}
49 changes: 47 additions & 2 deletions src/Xunit.DependencyInjection/DependencyInjectionTestInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,53 @@ public class DependencyInjectionTestInvoker(
: XunitTestInvoker(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments,
beforeAfterAttributes, aggregator, cancellationTokenSource)
{
private static readonly ActivitySource ActivitySource = new("Xunit.DependencyInjection", typeof(DependencyInjectionTestInvoker).Assembly.GetName().Version?.ToString());
private static readonly MethodInfo AsTaskMethod = new Func<ObjectMethodExecutorAwaitable, Task>(AsTask).Method;
private static readonly ActivitySource ActivitySource = new("Xunit.DependencyInjection",
typeof(DependencyInjectionTestInvoker).Assembly.GetName().Version?.ToString());

private static readonly MethodInfo AsTaskMethod;
private static readonly ConcurrentDictionary<Type, PropertyInfo[]> HasRequiredMembers = [];

static DependencyInjectionTestInvoker()
{
var method = AsTask;

AsTaskMethod = method.Method;
}

protected override object? CreateTestClass()
{
var testClassInstance = base.CreateTestClass();
if (testClassInstance == null ||
HasRequiredMembers.GetOrAdd(TestClass, GetRequiredProperties) is not { Length: > 0 } properties)
return testClassInstance;

foreach (var propertyInfo in properties)
{
if (propertyInfo.CanRead)
try
{
if (propertyInfo.GetValue(testClassInstance, null) != null) continue;
}
catch
{
continue;
}

propertyInfo.SetValue(testClassInstance, propertyInfo.PropertyType == typeof(ITestOutputHelper)
? provider.GetRequiredService<ITestOutputHelperAccessor>().Output
: provider.GetRequiredService(propertyInfo.PropertyType));
}

return testClassInstance;
}

private static PropertyInfo[] GetRequiredProperties(Type testClass) =>
!testClass.HasRequiredMemberAttribute() || testClass.GetConstructors().FirstOrDefault(static ci =>
!ci.IsStatic && ci.IsPublic) is not { } ci || ci.HasSetsRequiredMembersAttribute()
? []
: testClass.GetProperties()
.Where(p => p.SetMethod is { IsPublic: true } && p.HasRequiredMemberAttribute())
.ToArray();

/// <inheritdoc/>
protected override async Task<decimal> InvokeTestMethodAsync(object? testClassInstance)
Expand Down
22 changes: 22 additions & 0 deletions src/Xunit.DependencyInjection/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Xunit.DependencyInjection;

internal static class ReflectionExtensions
{
public static bool HasRequiredMemberAttribute(this Type type)
{
for (var currentType = type; currentType is not null && currentType != typeof(object);
currentType = currentType.BaseType)
if (currentType.CustomAttributes.Any(cad =>
cad.AttributeType.FullName == "System.Runtime.CompilerServices.RequiredMemberAttribute"))
return true;

return false;
}

public static bool HasRequiredMemberAttribute(this PropertyInfo propertyInfo) => propertyInfo.CustomAttributes.Any(
cad => cad.AttributeType.FullName == "System.Runtime.CompilerServices.RequiredMemberAttribute");

public static bool HasSetsRequiredMembersAttribute(this ConstructorInfo constructorInfo) =>
constructorInfo.CustomAttributes.Any(cad =>
cad.AttributeType.FullName == "System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute");
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

Release notes:

9.6: Support required property on test class.
9.5: Add support for BuildHostApplicationBuilder, fixing TheoryData of T evaluation.
9.4: Add ITestClassOrderer, Support registration ITestCollectionOrderer and ITestCaseOrderer.
9.3: Support xunit 2.8.0.
Expand All @@ -23,9 +24,9 @@ Release notes:
8.1: Startup allow static method or class (like Asp.Net Core startup).
8.0: New feature: Support multiple startup.</Description>
<PackageTags>xunit ioc di DependencyInjection test</PackageTags>
<Version>9.5.0</Version>
<Version>9.6.0</Version>
<PackageReleaseNotes>$(Description)</PackageReleaseNotes>
<PolySharpExcludeGeneratedTypes>System.Runtime.CompilerServices.RequiresLocationAttribute</PolySharpExcludeGeneratedTypes>
<PolySharpExcludeGeneratedTypes>System.Runtime.CompilerServices.RequiresLocationAttribute;System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute</PolySharpExcludeGeneratedTypes>
</PropertyGroup>

<ItemGroup>
Expand Down
63 changes: 63 additions & 0 deletions test/Xunit.DependencyInjection.Test/RequiredMemberTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System.Diagnostics.CodeAnalysis;

namespace Xunit.DependencyInjection.Test;

public class RequiredMemberTest
{
public required ITestOutputHelper Output { get; set; }

public required IDependency Dependency { get; init; }

[Fact]
public void Test()
{
Assert.NotNull(Output);
Assert.NotNull(Dependency);
}
}

public class SetsRequiredMembersTest
{
private readonly bool _isCtorSet;
private ITestOutputHelper _output;
private readonly IDependency _dependency;

[method: SetsRequiredMembers]
public SetsRequiredMembersTest(ITestOutputHelper output, IDependency dependency)
{
_output = Output = output;
_dependency = Dependency = dependency;

_isCtorSet = true;
}

public required ITestOutputHelper Output
{
get => _output;
set
{
if (_output != null) throw new InvalidOperationException();

_output = value;
}
}

public required IDependency Dependency
{
get => _dependency;
init
{
if (_dependency != null) throw new InvalidOperationException();

_dependency = value;
}
}

[Fact]
public void Test()
{
Assert.True(_isCtorSet);
Assert.NotNull(Output);
Assert.NotNull(Dependency);
}
}

0 comments on commit 51348a3

Please sign in to comment.