Skip to content

Commit ee43081

Browse files
committed
xunit test runner
1 parent ad99ca0 commit ee43081

File tree

5 files changed

+264
-0
lines changed

5 files changed

+264
-0
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using FluentAssertions;
5+
using Xunit;
6+
7+
namespace XunitCustomFramework;
8+
9+
public class CalculatorTests
10+
{
11+
[Theory]
12+
[InlineData(1, -1)]
13+
[InlineData(5, -1)]
14+
[InlineData(-1, 1)]
15+
public void WhenMultiplyingByANegativeTheResultIsNegative(int value1, int value2)
16+
{
17+
Calculator.Multiply(value1, value2).Should().BeNegative();
18+
}
19+
20+
[Theory]
21+
[InlineData(-1, -1)]
22+
[InlineData(-5, -1)]
23+
[InlineData(-1, -int.MaxValue)]
24+
public void WhenMultiplyingByTwoNegativesTheResultIsPositive(int value1, int value2)
25+
{
26+
Calculator.Multiply(value1, value2).Should().BePositive();
27+
}
28+
29+
[Theory]
30+
[InlineData(1, 0)]
31+
[InlineData(0, 1)]
32+
[InlineData(0, 0)]
33+
[InlineData(0, -5)]
34+
public void WhenMultiplyingBy0TheResultIs0(int value1, int value2)
35+
{
36+
Calculator.Multiply(value1, value2).Should().Be(0);
37+
}
38+
39+
[Fact]
40+
public void VerySlowTest()
41+
{
42+
Thread.Sleep(TimeSpan.FromMinutes(3));
43+
}
44+
}
45+
46+
47+
public class Calculator
48+
{
49+
public static int Multiply(int a, int b) => a * b;
50+
}
51+
52+
public class TestHostedService
53+
{
54+
private readonly IServiceProvider _services;
55+
public TestHostedService(IServiceProvider services)
56+
{
57+
_services = services;
58+
59+
}
60+
61+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
62+
{
63+
if (!await WaitForAppStartup(new CancellationTokenSource().Token, stoppingToken))
64+
{
65+
return;
66+
}
67+
68+
PrintAddresses(_services);
69+
await DoSomethingAsync();
70+
}
71+
72+
static async Task<bool> WaitForAppStartup(CancellationToken lifetime, CancellationToken stoppingToken)
73+
{
74+
var startedSource = new TaskCompletionSource();
75+
var cancelledSource = new TaskCompletionSource();
76+
77+
using var registration1 = lifetime.Register(() => startedSource.SetResult());
78+
using var registration2 = stoppingToken.Register(() => cancelledSource.SetResult());
79+
80+
Task completedTask = await Task.WhenAny(
81+
startedSource.Task,
82+
cancelledSource.Task).ConfigureAwait(false);
83+
84+
// If the completed tasks was the "app started" task, return true, otherwise false
85+
return completedTask == startedSource.Task;
86+
}
87+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using Xunit.Abstractions;
8+
using Xunit.Sdk;
9+
10+
[assembly:Xunit.TestFramework("XunitCustomFramework.CustomTestFramework", "XunitCustomFramework")]
11+
12+
namespace XunitCustomFramework;
13+
14+
public class CustomTestFramework : XunitTestFramework
15+
{
16+
public CustomTestFramework(IMessageSink messageSink)
17+
: base(messageSink)
18+
{
19+
messageSink.OnMessage(new DiagnosticMessage("Using CustomTestFramework"));
20+
}
21+
22+
protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName)
23+
=> new CustomExecutor(assemblyName, SourceInformationProvider, DiagnosticMessageSink);
24+
25+
private class CustomExecutor : XunitTestFrameworkExecutor
26+
{
27+
public CustomExecutor(AssemblyName assemblyName, ISourceInformationProvider sourceInformationProvider, IMessageSink diagnosticMessageSink)
28+
: base(assemblyName, sourceInformationProvider, diagnosticMessageSink)
29+
{
30+
}
31+
32+
protected override async void RunTestCases(IEnumerable<IXunitTestCase> testCases, IMessageSink executionMessageSink, ITestFrameworkExecutionOptions executionOptions)
33+
{
34+
using var assemblyRunner = new CustomAssemblyRunner(TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink, executionOptions);
35+
await assemblyRunner.RunAsync();
36+
}
37+
}
38+
39+
private class CustomAssemblyRunner : XunitTestAssemblyRunner
40+
{
41+
public CustomAssemblyRunner(ITestAssembly testAssembly, IEnumerable<IXunitTestCase> testCases, IMessageSink diagnosticMessageSink, IMessageSink executionMessageSink, ITestFrameworkExecutionOptions executionOptions)
42+
: base(testAssembly, testCases, diagnosticMessageSink, executionMessageSink, executionOptions)
43+
{
44+
}
45+
46+
protected override Task<RunSummary> RunTestCollectionAsync(IMessageBus messageBus, ITestCollection testCollection, IEnumerable<IXunitTestCase> testCases, CancellationTokenSource cancellationTokenSource)
47+
=> new CustomTestCollectionRunner(testCollection, testCases, DiagnosticMessageSink, messageBus, TestCaseOrderer, new ExceptionAggregator(Aggregator), cancellationTokenSource).RunAsync();
48+
}
49+
50+
private class CustomTestCollectionRunner : XunitTestCollectionRunner
51+
{
52+
public CustomTestCollectionRunner(ITestCollection testCollection, IEnumerable<IXunitTestCase> testCases, IMessageSink diagnosticMessageSink, IMessageBus messageBus, ITestCaseOrderer testCaseOrderer, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource)
53+
: base(testCollection, testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, cancellationTokenSource)
54+
{
55+
}
56+
57+
protected override Task<RunSummary> RunTestClassAsync(ITestClass testClass, IReflectionTypeInfo @class, IEnumerable<IXunitTestCase> testCases)
58+
=> new CustomTestClassRunner(testClass, @class, testCases, DiagnosticMessageSink, MessageBus, TestCaseOrderer, new ExceptionAggregator(Aggregator), CancellationTokenSource, CollectionFixtureMappings)
59+
.RunAsync();
60+
}
61+
62+
private class CustomTestClassRunner : XunitTestClassRunner
63+
{
64+
public CustomTestClassRunner(ITestClass testClass, IReflectionTypeInfo @class, IEnumerable<IXunitTestCase> testCases, IMessageSink diagnosticMessageSink, IMessageBus messageBus, ITestCaseOrderer testCaseOrderer, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource, IDictionary<Type, object> collectionFixtureMappings)
65+
: base(testClass, @class, testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, cancellationTokenSource, collectionFixtureMappings)
66+
{
67+
}
68+
69+
protected override Task<RunSummary> RunTestMethodAsync(ITestMethod testMethod, IReflectionMethodInfo method, IEnumerable<IXunitTestCase> testCases, object[] constructorArguments)
70+
=> new CustomTestMethodRunner(testMethod, this.Class, method, testCases, this.DiagnosticMessageSink, this.MessageBus, new ExceptionAggregator(this.Aggregator), this.CancellationTokenSource, constructorArguments)
71+
.RunAsync();
72+
}
73+
74+
private class CustomTestMethodRunner : XunitTestMethodRunner
75+
{
76+
private readonly IMessageSink _diagnosticMessageSink;
77+
78+
public CustomTestMethodRunner(ITestMethod testMethod, IReflectionTypeInfo @class, IReflectionMethodInfo method, IEnumerable<IXunitTestCase> testCases, IMessageSink diagnosticMessageSink, IMessageBus messageBus, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource, object[] constructorArguments)
79+
: base(testMethod, @class, method, testCases, diagnosticMessageSink, messageBus, aggregator, cancellationTokenSource, constructorArguments)
80+
{
81+
_diagnosticMessageSink = diagnosticMessageSink;
82+
}
83+
84+
protected override async Task<RunSummary> RunTestCaseAsync(IXunitTestCase testCase)
85+
{
86+
var parameters = string.Empty;
87+
88+
if (testCase.TestMethodArguments != null)
89+
{
90+
parameters = string.Join(", ", testCase.TestMethodArguments.Select(a => a?.ToString() ?? "null"));
91+
}
92+
93+
var test = $"{TestMethod.TestClass.Class.Name}.{TestMethod.Method.Name}({parameters})";
94+
95+
_diagnosticMessageSink.OnMessage(new DiagnosticMessage($"STARTED: {test}"));
96+
97+
var deadline = TimeSpan.FromMinutes(2);
98+
using var timer = new Timer(
99+
_ => _diagnosticMessageSink.OnMessage(new DiagnosticMessage($"WARNING: {test} has been running for more than {deadline.TotalMinutes:N0} minutes")),
100+
null,
101+
deadline,
102+
Timeout.InfiniteTimeSpan);
103+
104+
try
105+
{
106+
var result = await base.RunTestCaseAsync(testCase);
107+
108+
var status = result.Failed > 0
109+
? "FAILURE"
110+
: (result.Skipped > 0 ? "SKIPPED" : "SUCCESS");
111+
112+
_diagnosticMessageSink.OnMessage(new DiagnosticMessage($"{status}: {test} ({result.Time}s)"));
113+
114+
return result;
115+
}
116+
catch (Exception ex)
117+
{
118+
_diagnosticMessageSink.OnMessage(new DiagnosticMessage($"ERROR: {test} ({ex.Message})"));
119+
throw;
120+
}
121+
}
122+
}
123+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net6.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
7+
<IsPackable>false</IsPackable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="fluentassertions" Version="6.5.0" />
12+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
13+
<PackageReference Include="xunit" Version="2.4.1" />
14+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
15+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
16+
<PrivateAssets>all</PrivateAssets>
17+
</PackageReference>
18+
<PackageReference Include="coverlet.collector" Version="3.1.0">
19+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
20+
<PrivateAssets>all</PrivateAssets>
21+
</PackageReference>
22+
</ItemGroup>
23+
24+
<ItemGroup>
25+
<None Update="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
26+
</ItemGroup>
27+
28+
</Project>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.30114.105
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XunitCustomFramework", "XunitCustomFramework.csproj", "{686602AF-C177-46B0-88C0-4EFEC6C42993}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(SolutionProperties) = preSolution
14+
HideSolutionNode = FALSE
15+
EndGlobalSection
16+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
17+
{686602AF-C177-46B0-88C0-4EFEC6C42993}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
18+
{686602AF-C177-46B0-88C0-4EFEC6C42993}.Debug|Any CPU.Build.0 = Debug|Any CPU
19+
{686602AF-C177-46B0-88C0-4EFEC6C42993}.Release|Any CPU.ActiveCfg = Release|Any CPU
20+
{686602AF-C177-46B0-88C0-4EFEC6C42993}.Release|Any CPU.Build.0 = Release|Any CPU
21+
EndGlobalSection
22+
EndGlobal
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
3+
"diagnosticMessages": true
4+
}

0 commit comments

Comments
 (0)