Skip to content

Commit bad2ada

Browse files
committed
2 parents 2cb85db + 7187d2f commit bad2ada

18 files changed

+468
-7
lines changed

.github/copilot-instructions.md

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
# Xunit.DependencyInjection
2+
3+
Xunit.DependencyInjection is a .NET library that enables Microsoft.Extensions.DependencyInjection to resolve xUnit test cases. It provides dependency injection for xUnit tests with support for multiple target frameworks and integrates with ASP.NET Core TestHost.
4+
5+
Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
6+
7+
## Working Effectively
8+
9+
### CRITICAL REQUIREMENTS
10+
**STOP**: Do not proceed without .NET 10.x preview SDK. All build/test commands will fail otherwise.
11+
12+
### Environment Setup
13+
- **CRITICAL**: Install .NET 10.x preview SDK. This project CANNOT be built without .NET 10.x preview.
14+
```bash
15+
# Check current version
16+
dotnet --version
17+
# Must show 10.x.x preview version for building to work
18+
19+
# If you see 8.x.x or 9.x.x, all builds will fail with:
20+
# "error NU1011: Centrally defined floating package versions are not allowed"
21+
```
22+
- The project multi-targets .NET Framework 4.7.2, .NET 8.0, and .NET 9.0 but requires .NET 10.x SDK to build
23+
- Uses Central Package Management with floating versions (requires .NET 10.x preview)
24+
- **DO NOT ATTEMPT TO BUILD** without .NET 10.x preview - it will fail consistently
25+
26+
### Build Process
27+
- **NEVER CANCEL builds or test runs** - they can take significant time and must complete
28+
- Build the entire solution:
29+
```bash
30+
dotnet build -c Release -v n
31+
```
32+
**TIMING**: Build takes approximately 5-15 minutes. NEVER CANCEL. Set timeout to 30+ minutes.
33+
- Individual project builds:
34+
```bash
35+
dotnet build src/Xunit.DependencyInjection/Xunit.DependencyInjection.csproj -c Release -v n
36+
```
37+
- **IMPORTANT**: If floating package versions cause build failures, the issue is likely .NET SDK version mismatch. Ensure .NET 10.x preview is installed.
38+
39+
### Test Execution
40+
- Run core tests:
41+
```bash
42+
dotnet test -c Release --no-build ./test/Xunit.DependencyInjection.Test/Xunit.DependencyInjection.Test.csproj
43+
```
44+
**TIMING**: Test execution takes 3-10 minutes. NEVER CANCEL. Set timeout to 20+ minutes.
45+
- Run ASP.NET Core tests:
46+
```bash
47+
dotnet test -c Release --no-build ./test/Xunit.DependencyInjection.Test.AspNetCore/Xunit.DependencyInjection.Test.AspNetCore.csproj
48+
```
49+
- Run parallelization tests:
50+
```bash
51+
dotnet test -c Release --no-build ./test/Xunit.DependencyInjection.Test.Parallelization/Xunit.DependencyInjection.Test.Parallelization.csproj
52+
```
53+
- Run F# tests:
54+
```bash
55+
dotnet test -c Release --no-build ./test/Xunit.DependencyInjection.Test.FSharp/Xunit.DependencyInjection.Test.FSharp.fsproj
56+
```
57+
- **CRITICAL**: Always use `--no-build` when running tests after a successful build to save time.
58+
59+
### Packaging
60+
- Package all projects:
61+
```bash
62+
# Windows with Visual Studio
63+
powershell -ExecutionPolicy Bypass -File src/PackAllProject.ps1
64+
65+
# Alternative: dotnet pack
66+
dotnet pack -c Release -o .packages
67+
```
68+
- Packages are created in `.packages/` directory
69+
70+
## Validation Scenarios
71+
72+
### Basic Test Scenario
73+
After making changes, always validate with this complete scenario:
74+
1. Create a simple test project using the template:
75+
```bash
76+
dotnet new install src/Xunit.DependencyInjection.Template/bin/Release/Xunit.DependencyInjection.Template.*.nupkg
77+
mkdir TestValidation
78+
cd TestValidation
79+
dotnet new create xunit-di -n SampleTest
80+
cd SampleTest
81+
dotnet test
82+
```
83+
2. Verify the test project runs successfully with dependency injection
84+
3. Clean up: `dotnet new uninstall Xunit.DependencyInjection.Template`
85+
86+
### ASP.NET Core Integration Test
87+
1. Navigate to the MinimalAPI sample: `cd test/MinimalApiSample`
88+
2. The sample demonstrates integration with ASP.NET Core TestHost
89+
3. Verify tests in `Xunit.DependencyInjection.Test.AspNetCore` can access the sample via HTTP client injection
90+
91+
### Parallelization Test
92+
1. Run parallelization tests to ensure threading works correctly:
93+
```bash
94+
dotnet test ./test/Xunit.DependencyInjection.Test.Parallelization/Xunit.DependencyInjection.Test.Parallelization.csproj
95+
```
96+
2. Verify both sequential and parallel test execution modes work properly
97+
98+
## Key Project Structure
99+
100+
### Source Projects (`src/`)
101+
- `Xunit.DependencyInjection/` - Main library (multi-targets net472;net8.0)
102+
- `Xunit.DependencyInjection.Analyzer/` - Roslyn analyzer for compile-time validation
103+
- `Xunit.DependencyInjection.AspNetCoreTesting/` - ASP.NET Core TestHost integration
104+
- `Xunit.DependencyInjection.Logging/` - Microsoft.Extensions.Logging integration
105+
- `Xunit.DependencyInjection.Template/` - dotnet new template for creating test projects
106+
- `Xunit.DependencyInjection.StaFact/` - STA thread support for UI tests
107+
- `Xunit.DependencyInjection.Demystifier/` - Enhanced stack trace support
108+
- `Xunit.DependencyInjection.xRetry/` - Test retry functionality
109+
110+
### Test Projects (`test/`)
111+
- `Xunit.DependencyInjection.Test/` - Core functionality tests (dependency injection, startup patterns)
112+
- `Xunit.DependencyInjection.Test.AspNetCore/` - ASP.NET Core TestHost integration tests
113+
- `Xunit.DependencyInjection.Test.Parallelization/` - Parallel execution validation tests
114+
- `Xunit.DependencyInjection.Test.DisableTestParallelization/` - Sequential execution validation tests
115+
- `Xunit.DependencyInjection.Test.FSharp/` - F# language compatibility tests
116+
- `Xunit.DependencyInjection.Analyzer.Test/` - Roslyn analyzer validation tests
117+
- `MinimalApiSample/` - Sample ASP.NET Core application used by integration tests
118+
119+
### Important Files
120+
- `Directory.Build.props` - Global MSBuild properties
121+
- `Directory.Packages.props` - Central package management (uses floating versions)
122+
- `global.json` - .NET SDK version specification
123+
- `Xunit.DependencyInjection.slnx` - Solution file (newer format)
124+
125+
## Common Development Tasks
126+
127+
### Template Development
128+
1. Navigate to template: `cd src/Xunit.DependencyInjection.Template/`
129+
2. Package template: `dotnet pack -o out`
130+
3. Install locally: `dotnet new install ./out/Xunit.DependencyInjection.Template.*.nupkg`
131+
4. Test template: `dotnet new create xunit-di -n TestProject`
132+
5. Uninstall: `dotnet new uninstall Xunit.DependencyInjection.Template`
133+
134+
### Running Sample Applications
135+
- MinimalAPI sample is not directly runnable as a web app - it's designed for testing via TestHost
136+
- Test projects demonstrate various usage patterns - examine test classes for implementation examples
137+
138+
### Code Analysis
139+
- The project includes a Roslyn analyzer that validates Startup class configurations
140+
- Analyzer tests are in `test/Xunit.DependencyInjection.Analyzer.Test/`
141+
- Always run analyzer tests when modifying analyzer rules
142+
143+
## Troubleshooting
144+
145+
### Build Issues
146+
- **"Centrally defined floating package versions are not allowed"**: The most common error. Upgrade to .NET 10.x preview SDK immediately.
147+
- **"CS0106: The modifier 'public' is not valid for this item"**: Language version issue - this project uses C# 14 features that require .NET 10.x preview SDK
148+
- **Cannot restore packages**: Verify internet connectivity and NuGet package sources
149+
- **Projects fail to build on .NET 8.x/9.x**: Expected behavior. This project is designed for .NET 10.x preview only.
150+
151+
### Test Issues
152+
- **Tests hang or timeout**: Allow sufficient time - some tests verify timing behavior and intentionally delay
153+
- **Parallelization tests fail**: May indicate threading issues - check test output for timing information
154+
- **ASP.NET Core tests fail**: Verify MinimalApiSample builds correctly as a dependency
155+
156+
### Template Issues
157+
- **"Template not found"**: Ensure template is packaged and installed correctly
158+
- **Template creates project with wrong framework**: Use `-f` parameter to specify target framework
159+
160+
## Common Commands Output Reference
161+
162+
### Repository Structure
163+
```
164+
ls -la /
165+
.editorconfig
166+
.git/
167+
.github/
168+
.gitignore
169+
azure-pipelines.yml
170+
Directory.Build.props
171+
Directory.Packages.props
172+
global.json
173+
README.md
174+
src/
175+
test/
176+
Xunit.DependencyInjection.slnx
177+
```
178+
179+
### Key Configuration Files
180+
```
181+
cat global.json
182+
{
183+
"sdk": {
184+
"version": "10.0",
185+
"rollForward": "latestMajor",
186+
"allowPrerelease": true
187+
}
188+
}
189+
```
190+
191+
### Source Project List
192+
```
193+
find src/ -name "*.csproj" -o -name "*.fsproj"
194+
src/Xunit.DependencyInjection/Xunit.DependencyInjection.csproj
195+
src/Xunit.DependencyInjection.Analyzer/Xunit.DependencyInjection.Analyzer.csproj
196+
src/Xunit.DependencyInjection.AspNetCoreTesting/Xunit.DependencyInjection.AspNetCoreTesting.csproj
197+
src/Xunit.DependencyInjection.Demystifier/Xunit.DependencyInjection.Demystifier.csproj
198+
src/Xunit.DependencyInjection.Logging/Xunit.DependencyInjection.Logging.csproj
199+
src/Xunit.DependencyInjection.StaFact/Xunit.DependencyInjection.StaFact.csproj
200+
src/Xunit.DependencyInjection.Template/Xunit.DependencyInjection.Template.csproj
201+
src/Xunit.DependencyInjection.xRetry/Xunit.DependencyInjection.xRetry.csproj
202+
```
203+
204+
## Development Guidelines
205+
206+
### When Modifying Code
207+
1. Always build the entire solution first to establish baseline
208+
2. Run all relevant test suites for the area you're changing
209+
3. Validate template functionality if changes affect project generation
210+
4. Test both .NET Framework 4.7.2 and .NET 8.0 targets where applicable
211+
212+
### CI/CD Validation
213+
- GitHub Actions and Azure Pipelines both expect .NET 10.x preview
214+
- All test projects must pass on Windows, Linux, and macOS
215+
- Package generation must succeed for all projects
216+
217+
### Performance Considerations
218+
- Dependency injection setup happens once per test class
219+
- Parallelization can be controlled via `ParallelizationMode` property
220+
- Use `[DisableParallelization]` attribute for tests that require sequential execution

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
77
## How to use
88

9-
Install the [Nuget](https://www.nuget.org/packages/Xunit.DependencyInjection) package.
9+
[![Xunit.DependencyInjection NuGet](https://img.shields.io/nuget/v/Xunit.DependencyInjection)](https://www.nuget.org/packages/Xunit.DependencyInjection) [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/pengweiqhca/xunit.dependencyinjection)
10+
11+
Install the [NuGet](https://www.nuget.org/packages/Xunit.DependencyInjection) package.
1012

1113
```sh
1214
dotnet add package Xunit.DependencyInjection

src/Xunit.DependencyInjection.Analyzer/XunitDependencyInjectionAnalyzer.cs

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,27 +28,31 @@ private sealed class SymbolAnalyzer
2828
private readonly INamedTypeSymbol _serviceCollection;
2929
private readonly INamedTypeSymbol _hostBuilderContext;
3030
private readonly INamedTypeSymbol _host;
31+
private readonly INamedTypeSymbol _hostApplicationBuilder;
32+
private readonly INamedTypeSymbol _iHostApplicationBuilder;
3133
private readonly string _startupName;
3234

3335
private SymbolAnalyzer(INamedTypeSymbol type1, INamedTypeSymbol type2, INamedTypeSymbol type3,
34-
INamedTypeSymbol type4, INamedTypeSymbol type5, string startupName)
36+
INamedTypeSymbol type4, INamedTypeSymbol type5, INamedTypeSymbol type6, INamedTypeSymbol type7, string startupName)
3537
{
3638
_hostBuilder = type1;
3739
_assemblyName = type2;
3840
_serviceCollection = type3;
3941
_hostBuilderContext = type4;
4042
_host = type5;
43+
_hostApplicationBuilder = type6;
44+
_iHostApplicationBuilder = type7;
4145
_startupName = startupName;
4246
}
4347

4448
public static void RegisterCompilationStartAction(CompilationStartAnalysisContext csac)
4549
{
46-
var (hostBuilder, assemblyName, serviceCollection, hostBuilderContext, hostBuild, startupTypeAttribute) =
50+
var (hostBuilder, assemblyName, serviceCollection, hostBuilderContext, hostBuild, hostApplicationBuilder, iHostApplicationBuilder, startupTypeAttribute) =
4751
GetTypeSymbols(csac.Compilation);
4852

4953
if (hostBuilder == null || assemblyName == null || serviceCollection == null ||
50-
hostBuilderContext == null ||
51-
hostBuild == null || startupTypeAttribute == null) return;
54+
hostBuilderContext == null || hostBuild == null || hostApplicationBuilder == null ||
55+
iHostApplicationBuilder == null || startupTypeAttribute == null) return;
5256

5357
var sta = csac.Compilation.Assembly.GetAttributes()
5458
.FirstOrDefault(attr => SymbolComparer.Equals(attr.AttributeClass, startupTypeAttribute));
@@ -75,12 +79,12 @@ sta.ConstructorArguments[1].Value is string ns &&
7579

7680
// See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Analyzer%20Actions%20Semantics.md for more information
7781
csac.RegisterSymbolAction(
78-
new SymbolAnalyzer(hostBuilder, assemblyName, serviceCollection, hostBuilderContext, hostBuild, startupName)
82+
new SymbolAnalyzer(hostBuilder, assemblyName, serviceCollection, hostBuilderContext, hostBuild, hostApplicationBuilder, iHostApplicationBuilder, startupName)
7983
.AnalyzeSymbol, SymbolKind.Method, SymbolKind.NamedType);
8084
}
8185

8286
private static (INamedTypeSymbol? HostBuilder, INamedTypeSymbol? AssemblyName, INamedTypeSymbol?
83-
ServiceCollection, INamedTypeSymbol? HostBuilderContext, INamedTypeSymbol? HostBuild, INamedTypeSymbol?
87+
ServiceCollection, INamedTypeSymbol? HostBuilderContext, INamedTypeSymbol? HostBuild, INamedTypeSymbol? HostApplicationBuilder, INamedTypeSymbol? IHostApplicationBuilder, INamedTypeSymbol?
8488
StartupTypeAttribute) GetTypeSymbols(Compilation compilation)
8589
{
8690
var ass = compilation.References
@@ -93,6 +97,8 @@ private static (INamedTypeSymbol? HostBuilder, INamedTypeSymbol? AssemblyName, I
9397
GetTypeSymbol("Microsoft.Extensions.DependencyInjection.IServiceCollection"),
9498
GetTypeSymbol("Microsoft.Extensions.Hosting.HostBuilderContext"),
9599
GetTypeSymbol("Microsoft.Extensions.Hosting.IHost"),
100+
GetTypeSymbol("Microsoft.Extensions.Hosting.HostApplicationBuilder"),
101+
GetTypeSymbol("Microsoft.Extensions.Hosting.IHostApplicationBuilder"),
96102
GetTypeSymbol("Xunit.DependencyInjection.StartupTypeAttribute"));
97103

98104
INamedTypeSymbol? GetTypeSymbol(string name) => ass
@@ -118,6 +124,10 @@ private void AnalyzeSymbol(SymbolAnalysisContext context)
118124
AnalyzeOverride(context, type, "ConfigureHost");
119125
AnalyzeOverride(context, type, "ConfigureServices");
120126
AnalyzeOverride(context, type, "Configure");
127+
AnalyzeOverride(context, type, "BuildHost");
128+
AnalyzeOverride(context, type, "CreateHostApplicationBuilder");
129+
AnalyzeOverride(context, type, "ConfigureHostApplicationBuilder");
130+
AnalyzeOverride(context, type, "BuildHostApplicationBuilder");
121131

122132
return;
123133
case IMethodSymbol method:
@@ -141,6 +151,12 @@ private void AnalyzeSymbol(SymbolAnalysisContext context)
141151
AnalyzeConfigure(context, method);
142152
else if ("BuildHost".Equals(method.Name, StringComparison.OrdinalIgnoreCase))
143153
AnalyzeBuildHost(context, method);
154+
else if ("CreateHostApplicationBuilder".Equals(method.Name, StringComparison.OrdinalIgnoreCase))
155+
AnalyzeCreateHostApplicationBuilder(context, method);
156+
else if ("ConfigureHostApplicationBuilder".Equals(method.Name, StringComparison.OrdinalIgnoreCase))
157+
AnalyzeConfigureHostApplicationBuilder(context, method);
158+
else if ("BuildHostApplicationBuilder".Equals(method.Name, StringComparison.OrdinalIgnoreCase))
159+
AnalyzeBuildHostApplicationBuilder(context, method);
144160

145161
return;
146162
}
@@ -249,5 +265,35 @@ private void AnalyzeBuildHost(SymbolAnalysisContext context, IMethodSymbol metho
249265
context.ReportDiagnostic(Diagnostic.Create(Rules.SingleParameter, method.Locations[0], method.Name,
250266
_hostBuilder.Name));
251267
}
268+
269+
private void AnalyzeCreateHostApplicationBuilder(SymbolAnalysisContext context, IMethodSymbol method)
270+
{
271+
AnalyzeReturnType(context, method, _hostApplicationBuilder);
272+
273+
if (method.Parameters.Length == 0) return;
274+
275+
if (method.Parameters.Length > 1 || !SymbolComparer.Equals(_assemblyName, method.Parameters[0].Type))
276+
context.ReportDiagnostic(Diagnostic.Create(Rules.ParameterlessOrSingleParameter, method.Locations[0],
277+
method.Name, _assemblyName.Name));
278+
}
279+
280+
private void AnalyzeConfigureHostApplicationBuilder(SymbolAnalysisContext context, IMethodSymbol method)
281+
{
282+
AnalyzeReturnType(context, method, null);
283+
284+
var parameters = method.Parameters;
285+
if (parameters.Length != 1 || !SymbolComparer.Equals(parameters[0].Type, _iHostApplicationBuilder))
286+
context.ReportDiagnostic(Diagnostic.Create(Rules.SingleParameter, method.Locations[0], method.Name,
287+
_iHostApplicationBuilder.Name));
288+
}
289+
290+
private void AnalyzeBuildHostApplicationBuilder(SymbolAnalysisContext context, IMethodSymbol method)
291+
{
292+
AnalyzeReturnType(context, method, _host);
293+
294+
if (method.Parameters.Length != 1 || !SymbolComparer.Equals(_hostApplicationBuilder, method.Parameters[0].Type))
295+
context.ReportDiagnostic(Diagnostic.Create(Rules.SingleParameter, method.Locations[0], method.Name,
296+
_hostApplicationBuilder.Name));
297+
}
252298
}
253299
}

0 commit comments

Comments
 (0)