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+ }
0 commit comments