diff --git a/src/PackAllProject.ps1 b/src/PackAllProject.ps1 new file mode 100644 index 0000000..7dcf033 --- /dev/null +++ b/src/PackAllProject.ps1 @@ -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 + } + } +} diff --git a/src/Xunit.DependencyInjection/DependencyInjectionTestInvoker.cs b/src/Xunit.DependencyInjection/DependencyInjectionTestInvoker.cs index 2ed43cf..6bac308 100644 --- a/src/Xunit.DependencyInjection/DependencyInjectionTestInvoker.cs +++ b/src/Xunit.DependencyInjection/DependencyInjectionTestInvoker.cs @@ -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(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 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().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(); /// protected override async Task InvokeTestMethodAsync(object? testClassInstance) diff --git a/src/Xunit.DependencyInjection/ReflectionExtensions.cs b/src/Xunit.DependencyInjection/ReflectionExtensions.cs new file mode 100644 index 0000000..9c3a65e --- /dev/null +++ b/src/Xunit.DependencyInjection/ReflectionExtensions.cs @@ -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"); +} diff --git a/src/Xunit.DependencyInjection/Xunit.DependencyInjection.csproj b/src/Xunit.DependencyInjection/Xunit.DependencyInjection.csproj index 0412d8e..3cfb9e7 100644 --- a/src/Xunit.DependencyInjection/Xunit.DependencyInjection.csproj +++ b/src/Xunit.DependencyInjection/Xunit.DependencyInjection.csproj @@ -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. @@ -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. xunit ioc di DependencyInjection test - 9.5.0 + 9.6.0 $(Description) - System.Runtime.CompilerServices.RequiresLocationAttribute + System.Runtime.CompilerServices.RequiresLocationAttribute;System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute diff --git a/test/Xunit.DependencyInjection.Test/RequiredMemberTest.cs b/test/Xunit.DependencyInjection.Test/RequiredMemberTest.cs new file mode 100644 index 0000000..8182769 --- /dev/null +++ b/test/Xunit.DependencyInjection.Test/RequiredMemberTest.cs @@ -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); + } +}