Skip to content

Commit 0417080

Browse files
authored
feat: add xunit docs (#366)
1 parent 68c4d93 commit 0417080

File tree

11 files changed

+351
-2
lines changed

11 files changed

+351
-2
lines changed

FluentAssertions.Analyzers.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentAssertions.Analyzers.
3434
EndProject
3535
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs.Nunit3", "src\FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs.Nunit3\FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs.Nunit3.csproj", "{FBCDB423-7729-42CD-9279-2D0BECE37907}"
3636
EndProject
37+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs.Xunit", "src\FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs.Xunit\FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs.Xunit.csproj", "{94D58487-F555-45A1-8076-511D1792C1DE}"
38+
EndProject
3739
Global
3840
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3941
Debug|Any CPU = Debug|Any CPU
@@ -72,6 +74,10 @@ Global
7274
{FBCDB423-7729-42CD-9279-2D0BECE37907}.Debug|Any CPU.Build.0 = Debug|Any CPU
7375
{FBCDB423-7729-42CD-9279-2D0BECE37907}.Release|Any CPU.ActiveCfg = Release|Any CPU
7476
{FBCDB423-7729-42CD-9279-2D0BECE37907}.Release|Any CPU.Build.0 = Release|Any CPU
77+
{94D58487-F555-45A1-8076-511D1792C1DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
78+
{94D58487-F555-45A1-8076-511D1792C1DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
79+
{94D58487-F555-45A1-8076-511D1792C1DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
80+
{94D58487-F555-45A1-8076-511D1792C1DE}.Release|Any CPU.Build.0 = Release|Any CPU
7581
EndGlobalSection
7682
GlobalSection(SolutionProperties) = preSolution
7783
HideSolutionNode = FALSE
@@ -85,6 +91,7 @@ Global
8591
{2871B22C-AEFC-4C33-9BBF-695699E61B57} = {72192514-FA22-4699-8EE1-39D34CFE1025}
8692
{A819A8A5-C8F7-46AD-B0F2-44868070A188} = {8EFE7955-E63C-4055-A9FF-76C7CE0A1151}
8793
{FBCDB423-7729-42CD-9279-2D0BECE37907} = {8EFE7955-E63C-4055-A9FF-76C7CE0A1151}
94+
{94D58487-F555-45A1-8076-511D1792C1DE} = {8EFE7955-E63C-4055-A9FF-76C7CE0A1151}
8895
EndGlobalSection
8996
GlobalSection(ExtensibilityGlobals) = postSolution
9097
SolutionGuid = {4BF3D005-625C-4CEC-B3FB-298B956402BE}

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ dotnet add package FluentAssertions.Analyzers
3434
- [MsTest Analyzer Docs](docs/MsTestAnalyzer.md)
3535
- [NUnit4 Analyzer Docs](docs/Nunit4Analyzer.md)
3636
- [NUnit3 Analyzer Docs](docs/Nunit3Analyzer.md)
37+
- [Xunit Analyzer Docs](docs/XunitAnalyzer.md)
3738

3839
## Getting Started
3940

docs/XunitAnalyzer.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<!--
2+
This is a generated file, please edit src\FluentAssertions.Analyzers.FluentAssertionAnalyzerDocsGenerator\DocsGenerator.cs to change the contents
3+
-->
4+
5+
# Xunit Analyzer Docs
6+
7+
- [AssertTrue](#scenario-asserttrue) - `flag.Should().BeTrue();`
8+
- [AssertFalse](#scenario-assertfalse) - `flag.Should().BeFalse();`
9+
10+
11+
## Scenarios
12+
13+
### scenario: AssertTrue
14+
15+
```cs
16+
// arrange
17+
var flag = true;
18+
19+
// old assertion:
20+
Assert.True(flag);
21+
22+
// new assertion:
23+
flag.Should().BeTrue();
24+
```
25+
26+
#### Failure messages
27+
28+
```cs
29+
var flag = false;
30+
31+
// old assertion:
32+
Assert.True(flag); /* fail message: Assert.True() Failure
33+
Expected: True
34+
Actual: False */
35+
36+
// new assertion:
37+
flag.Should().BeTrue(); /* fail message: Expected flag to be true, but found False. */
38+
```
39+
40+
### scenario: AssertFalse
41+
42+
```cs
43+
// arrange
44+
var flag = false;
45+
46+
// old assertion:
47+
Assert.False(flag);
48+
49+
// new assertion:
50+
flag.Should().BeFalse();
51+
```
52+
53+
#### Failure messages
54+
55+
```cs
56+
var flag = true;
57+
58+
// old assertion:
59+
Assert.False(flag); /* fail message: Assert.False() Failure
60+
Expected: False
61+
Actual: True */
62+
63+
// new assertion:
64+
flag.Should().BeFalse(); /* fail message: Expected flag to be false, but found True. */
65+
```
66+
67+

scripts/generate-docs.ps1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ function GenerateDocs {
2828

2929
GenerateDocs -project FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs
3030
GenerateDocs -project FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs.Nunit4
31-
GenerateDocs -project FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs.Nunit3
31+
GenerateDocs -project FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs.Nunit3
32+
GenerateDocs -project FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs.Xunit

scripts/run-docs-tests.ps1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,5 @@ function RunTestsAndValidate {
4343

4444
RunTestsAndValidate -project FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs
4545
RunTestsAndValidate -project FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs.Nunit4
46-
RunTestsAndValidate -project FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs.Nunit3
46+
RunTestsAndValidate -project FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs.Nunit3
47+
RunTestsAndValidate -project FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs.Xunit
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Reflection;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Xunit.Abstractions;
7+
using Xunit.Sdk;
8+
9+
[assembly: Xunit.TestFramework("FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs.CustomTestFramework", "FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs.Xunit")]
10+
11+
namespace FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs;
12+
13+
// Inspired by https://andrewlock.net/tracking-down-a-hanging-xunit-test-in-ci-building-a-custom-test-framework/ with a few more customizations
14+
public class CustomTestFramework : XunitTestFramework
15+
{
16+
public CustomTestFramework(IMessageSink messageSink) : base(messageSink)
17+
{
18+
}
19+
20+
protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName)
21+
=> new CustomExecutor(assemblyName, SourceInformationProvider, DiagnosticMessageSink);
22+
23+
private class CustomExecutor : XunitTestFrameworkExecutor
24+
{
25+
public CustomExecutor(AssemblyName assemblyName, ISourceInformationProvider sourceInformationProvider, IMessageSink diagnosticMessageSink)
26+
: base(assemblyName, sourceInformationProvider, diagnosticMessageSink)
27+
{
28+
}
29+
30+
protected override async void RunTestCases(IEnumerable<IXunitTestCase> testCases, IMessageSink executionMessageSink, ITestFrameworkExecutionOptions executionOptions)
31+
{
32+
using var assemblyRunner = new CustomAssemblyRunner(TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink, executionOptions);
33+
await assemblyRunner.RunAsync();
34+
}
35+
}
36+
37+
private class CustomAssemblyRunner : XunitTestAssemblyRunner
38+
{
39+
public CustomAssemblyRunner(ITestAssembly testAssembly, IEnumerable<IXunitTestCase> testCases, IMessageSink diagnosticMessageSink, IMessageSink executionMessageSink, ITestFrameworkExecutionOptions executionOptions)
40+
: base(testAssembly, testCases, diagnosticMessageSink, executionMessageSink, executionOptions)
41+
{
42+
}
43+
44+
protected override Task<RunSummary> RunTestCollectionAsync(IMessageBus messageBus, ITestCollection testCollection, IEnumerable<IXunitTestCase> testCases, CancellationTokenSource cancellationTokenSource)
45+
=> new CustomTestCollectionRunner(testCollection, testCases, DiagnosticMessageSink, messageBus, TestCaseOrderer, new ExceptionAggregator(Aggregator), cancellationTokenSource).RunAsync();
46+
}
47+
48+
private class CustomTestCollectionRunner : XunitTestCollectionRunner
49+
{
50+
public CustomTestCollectionRunner(ITestCollection testCollection, IEnumerable<IXunitTestCase> testCases, IMessageSink diagnosticMessageSink, IMessageBus messageBus, ITestCaseOrderer testCaseOrderer, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource)
51+
: base(testCollection, testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, cancellationTokenSource)
52+
{
53+
}
54+
55+
protected override Task<RunSummary> RunTestClassAsync(ITestClass testClass, IReflectionTypeInfo @class, IEnumerable<IXunitTestCase> testCases)
56+
=> new CustomTestClassRunner(testClass, @class, testCases, DiagnosticMessageSink, MessageBus, TestCaseOrderer, new ExceptionAggregator(Aggregator), CancellationTokenSource, CollectionFixtureMappings).RunAsync();
57+
}
58+
59+
private class CustomTestClassRunner : XunitTestClassRunner
60+
{
61+
public CustomTestClassRunner(ITestClass testClass, IReflectionTypeInfo @class, IEnumerable<IXunitTestCase> testCases, IMessageSink diagnosticMessageSink, IMessageBus messageBus, ITestCaseOrderer testCaseOrderer, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource, IDictionary<Type, object> collectionFixtureMappings)
62+
: base(testClass, @class, testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, cancellationTokenSource, collectionFixtureMappings)
63+
{
64+
}
65+
66+
protected override Task<RunSummary> RunTestMethodAsync(ITestMethod testMethod, IReflectionMethodInfo method, IEnumerable<IXunitTestCase> testCases, object[] constructorArguments)
67+
=> new CustomTestMethodRunner(testMethod, Class, method, testCases, DiagnosticMessageSink, MessageBus, new ExceptionAggregator(Aggregator), CancellationTokenSource, constructorArguments).RunAsync();
68+
}
69+
70+
private class CustomTestMethodRunner : XunitTestMethodRunner
71+
{
72+
private readonly object[] _constructorArguments;
73+
private readonly CancellationTokenSource _cancellationTokenSource;
74+
75+
public CustomTestMethodRunner(ITestMethod testMethod, IReflectionTypeInfo @class, IReflectionMethodInfo method, IEnumerable<IXunitTestCase> testCases, IMessageSink diagnosticMessageSink, IMessageBus messageBus, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource, object[] constructorArguments)
76+
: base(testMethod, @class, method, testCases, diagnosticMessageSink, messageBus, aggregator, cancellationTokenSource, constructorArguments)
77+
{
78+
_constructorArguments = constructorArguments;
79+
_cancellationTokenSource = cancellationTokenSource;
80+
}
81+
82+
protected override async Task<RunSummary> RunTestCaseAsync(IXunitTestCase testCase)
83+
{
84+
return await new CustomTestCaseRunner(testCase, testCase.DisplayName, testCase.SkipReason, _constructorArguments, testCase.TestMethodArguments, MessageBus, Aggregator, _cancellationTokenSource)
85+
.RunAsync();
86+
}
87+
}
88+
89+
private class CustomTestCaseRunner : XunitTestCaseRunner
90+
{ public CustomTestCaseRunner(IXunitTestCase testCase, string displayName, string skipReason, object[] constructorArguments, object[] testMethodArguments, IMessageBus messageBus, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) : base(testCase, displayName, skipReason, constructorArguments, testMethodArguments, messageBus, aggregator, cancellationTokenSource)
91+
{
92+
}
93+
94+
protected override XunitTestRunner CreateTestRunner(ITest test, IMessageBus messageBus, Type testClass, object[] constructorArguments, MethodInfo testMethod, object[] testMethodArguments, string skipReason, IReadOnlyList<BeforeAfterTestAttribute> beforeAfterAttributes, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource)
95+
{
96+
return new CustomTestRunner(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, skipReason, beforeAfterAttributes, aggregator, cancellationTokenSource);
97+
}
98+
}
99+
100+
private class CustomTestRunner : XunitTestRunner
101+
{
102+
public CustomTestRunner(ITest test, IMessageBus messageBus, Type testClass, object[] constructorArguments, MethodInfo testMethod, object[] testMethodArguments, string skipReason, IReadOnlyList<BeforeAfterTestAttribute> beforeAfterAttributes, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) : base(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, skipReason, beforeAfterAttributes, aggregator, cancellationTokenSource)
103+
{
104+
}
105+
106+
protected override Task<decimal> InvokeTestMethodAsync(ExceptionAggregator aggregator)
107+
{
108+
return new CustomTestInvoker(Test, MessageBus, TestClass, ConstructorArguments, TestMethod, TestMethodArguments, BeforeAfterAttributes, aggregator, CancellationTokenSource).RunAsync();
109+
}
110+
}
111+
112+
private class CustomTestInvoker : XunitTestInvoker
113+
{
114+
115+
public CustomTestInvoker(ITest test, IMessageBus messageBus, Type testClass, object[] constructorArguments, MethodInfo testMethod, object[] testMethodArguments, IReadOnlyList<BeforeAfterTestAttribute> beforeAfterAttributes, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) : base(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, beforeAfterAttributes, aggregator, cancellationTokenSource)
116+
{
117+
}
118+
119+
protected override object CallTestMethod(object testClassInstance)
120+
{
121+
var isExpectingException = TestMethod.GetCustomAttribute<ExpectedAssertionExceptionAttribute>() != null;
122+
123+
try
124+
{
125+
var result = base.CallTestMethod(testClassInstance);
126+
if (isExpectingException)
127+
{
128+
Aggregator.Add(new XunitException($"Expected exception of type {typeof(XunitException)}, but no exception was thrown."));
129+
}
130+
131+
return result;
132+
}
133+
catch (TargetInvocationException ex) when (ex.InnerException is XunitException)
134+
{
135+
if (!isExpectingException)
136+
{
137+
Aggregator.Add(ex.InnerException);
138+
}
139+
140+
return null;
141+
}
142+
}
143+
}
144+
}
145+
146+
public class ExpectedAssertionExceptionAttribute : Attribute
147+
{
148+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<IsPackable>false</IsPackable>
7+
<IsTestProject>true</IsTestProject>
8+
<GenerateProgramFile>false</GenerateProgramFile>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
13+
<PackageReference Include="xunit" Version="2.4.2" />
14+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
15+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
16+
<PrivateAssets>all</PrivateAssets>
17+
</PackageReference>
18+
<PackageReference Include="FluentAssertions" Version="6.12.0" />
19+
<PackageReference Include="coverlet.collector" Version="6.0.0" />
20+
</ItemGroup>
21+
22+
<ItemGroup>
23+
<ProjectReference Include="..\FluentAssertions.Analyzers.FluentAssertionAnalyzerDocsGenerator\FluentAssertions.Analyzers.FluentAssertionAnalyzerDocsGenerator.csproj" />
24+
<ProjectReference Include="..\FluentAssertions.Analyzers\FluentAssertions.Analyzers.csproj" />
25+
<Analyzer Include="..\FluentAssertions.Analyzers\bin\Debug\netstandard2.0\FluentAssertions.Analyzers.dll" />
26+
</ItemGroup>
27+
</Project>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
using System.IO;
3+
using System.Reflection;
4+
using System.Threading.Tasks;
5+
using FluentAssertions.Analyzers.FluentAssertionAnalyzerDocsGenerator;
6+
7+
namespace FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs;
8+
9+
public class Program
10+
{
11+
public static Task Main(string[] args) => ProgramUtils.RunMain<XunitDocsGenerator, XunitDocsVerifier>(args);
12+
13+
private class XunitDocsGenerator : DocsGenerator
14+
{
15+
protected override Assembly TestAssembly { get; } = typeof(Program).Assembly;
16+
protected override string TestAttribute => "Fact"; // Xunit.FactAttribute
17+
protected override string TestFile => Path.Join(Environment.CurrentDirectory, "XunitAnalyzerTests.cs");
18+
}
19+
private class XunitDocsVerifier : DocsVerifier
20+
{
21+
protected override string TestAttribute => "Fact"; // Xunit.FactAttribute
22+
protected override string TestFile => Path.Join(Environment.CurrentDirectory, "XunitAnalyzerTests.cs");
23+
}
24+
}

0 commit comments

Comments
 (0)