Skip to content

Commit

Permalink
Updated GetSUT to be thread safe (#167)
Browse files Browse the repository at this point in the history
* Updated GetSUT to use an exclusive lock

Fixes #166
  • Loading branch information
roryprimrose authored May 10, 2024
1 parent 0035842 commit b6bb50d
Show file tree
Hide file tree
Showing 13 changed files with 133 additions and 65 deletions.
22 changes: 11 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ jobs:
fileVersion: ${{ steps.gitversion.outputs.MajorMinorPatch }}
informationalVersion: ${{ steps.gitversion.outputs.InformationalVersion }}

- name: Setup dotnet v6.0
uses: actions/setup-dotnet@v3
- name: Setup dotnet v8.0
uses: actions/setup-dotnet@v4
with:
dotnet-version: '6.x' # SDK Version to use.
dotnet-version: '8.x' # SDK Version to use.

- name: Restore
run: dotnet restore
Expand All @@ -59,7 +59,7 @@ jobs:

- name: Generate coverage report
# run: reportgenerator -reports:**/coverage.cobertura.xml -targetdir:Report -reporttypes:HtmlInline_AzurePipelines;Cobertura
uses: danielpalme/[email protected].0
uses: danielpalme/[email protected].5
with:
reports: "**/coverage*cobertura.xml"
targetdir: "Report"
Expand All @@ -85,7 +85,7 @@ jobs:
dotnet pack "./${{ env.projectName }}.NSubstitute/${{ env.projectName }}.NSubstitute.csproj" -c Release --no-build --include-symbols -o $GITHUB_WORKSPACE/staging
- name: Publish build artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: packages
path: staging
Expand All @@ -95,7 +95,7 @@ jobs:
run: |
echo '${{ steps.gitversion.outputs.NuGetVersionV2 }}' > version.txt
- name: Upload version
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: version
path: version.txt
Expand All @@ -107,16 +107,16 @@ jobs:

steps:
- name: Download packages
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: packages

- name: Setup nuget
uses: nuget/setup-nuget@v1
uses: nuget/setup-nuget@v2

# Temporary steps while waiting for nuget client to support multiple package push
- name: Download version
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: version

Expand Down Expand Up @@ -149,12 +149,12 @@ jobs:
steps:

- name: Download packages
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: packages

- name: Download version
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: version

Expand Down
24 changes: 24 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project>
<!-- Central Package Management: https://learn.microsoft.com/en-us/nuget/consume-packages/Central-Package-Management -->
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
<ItemGroup Label="Package Versions used by this repository">
<!-- Direct dependencies -->
<PackageVersion Include="NSubstitute" Version="5.1.0" />
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
<PackageVersion Include="Neovolve.Logging.Xunit" Version="5.0.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageVersion Include="xunit" Version="2.8.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.0" PrivateAssets="All"
IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
<PackageVersion Include="coverlet.msbuild" Version="6.0.2" PrivateAssets="All"
IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
<!-- Overrides -->
</ItemGroup>
<ItemGroup Label="Global Package References, added to every project">
<GlobalPackageVersion Include="ReferenceTrimmer" Version="3.1.22" PrivateAssets="All" />
<GlobalPackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0" PrivateAssets="All" />
</ItemGroup>
</Project>
15 changes: 6 additions & 9 deletions Examples/Examples.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,15 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Divergic.Logging.Xunit" Version="4.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.msbuild" Version="6.0.0">
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Neovolve.Logging.Xunit" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.msbuild" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion Examples/KeyedServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public void CanConfigureKeyedServices()
actual.Should().Be(18);
}

protected override object ResolveService(ParameterInfo parameter)
protected override object? ResolveService(ParameterInfo parameter)
{
if (parameter.ParameterType != typeof(IDuplicateService))
{
Expand Down
2 changes: 1 addition & 1 deletion Examples/TestClassConstructorParameters.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
namespace Examples;

using System;
using Divergic.Logging.Xunit;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Neovolve.Logging.Xunit;
using NSubstitute;
using Xunit;
using Xunit.Abstractions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,18 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.msbuild" Version="6.0.0">
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.msbuild" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="NSubstitute" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<RootNamespace>NSubstitute</RootNamespace>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
Expand All @@ -28,7 +28,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="NSubstitute" />
</ItemGroup>

<ItemGroup>
Expand Down
17 changes: 7 additions & 10 deletions Neovolve.Streamline.UnitTests/Neovolve.Streamline.UnitTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,19 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Divergic.Logging.Xunit" Version="4.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.msbuild" Version="6.0.0">
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Neovolve.Logging.Xunit" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.msbuild" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="NSubstitute" />
</ItemGroup>

<ItemGroup>
Expand Down
34 changes: 33 additions & 1 deletion Neovolve.Streamline.UnitTests/TestsTests.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
namespace Neovolve.Streamline.UnitTests;

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Divergic.Logging.Xunit;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Neovolve.Logging.Xunit;
using NSubstitute;
using Xunit;
using Xunit.Abstractions;
Expand Down Expand Up @@ -393,6 +394,30 @@ public void ServiceThrowsExceptionWithNullKey()
action.Should().Throw<ArgumentNullException>();
}

[Fact]
public async Task SUTReturnsSameInstanceOnAllThreads()
{
var maxCount = 100;
var tasks = new List<Task<Target>>(maxCount);
var wrapper = new Wrapper();

for (var index = 0; index < maxCount; index++)
{
var task = GetSUT(wrapper);

tasks.Add(task);
}

var results = await Task.WhenAll(tasks);

var first = results[0];

foreach (var result in results)
{
result.Should().BeSameAs(first);
}
}

[Fact]
public void SUTThrowsExceptionWhenMultipleInternalConstructorsFound()
{
Expand Down Expand Up @@ -655,6 +680,13 @@ public void UseWithNullReturnsNull()
actual.Should().BeNull();
}

private async Task<Target> GetSUT(Wrapper wrapper)
{
await Task.Delay(10);

return wrapper.SUT;
}

// ReSharper disable once ClassNeverInstantiated.Local
private class DefaultConstructor
{
Expand Down
8 changes: 5 additions & 3 deletions Neovolve.Streamline.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30907.101
# Visual Studio Version 17
VisualStudioVersion = 17.9.34728.123
MinimumVisualStudioVersion = 15.0.26124.0
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neovolve.Streamline", "Neovolve.Streamline\Neovolve.Streamline.csproj", "{980EE2DF-8434-418A-88CA-876E99E48CC9}"
EndProject
Expand All @@ -16,8 +16,10 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3CC70DA2-2BA4-46D5-9667-D47A4C87ACC0}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
Directory.Packages.props = Directory.Packages.props
GitVersion.yml = GitVersion.yml
LICENSE.md = LICENSE.md
NuGet.config = NuGet.config
README.md = README.md
EndProjectSection
EndProject
Expand All @@ -35,7 +37,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neovolve.Streamline.UnitTes
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Unit Tests", "Unit Tests", "{06FA8D87-BF6A-4AC0-818A-4D2D51EE4CA2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neovolve.Streamline.NSubstitute.UnitTests", "Neovolve.Streamline.NSubstitute.UnitTests\Neovolve.Streamline.NSubstitute.UnitTests.csproj", "{2DA40BE0-BD60-4733-949D-F3D9DFBA098C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neovolve.Streamline.NSubstitute.UnitTests", "Neovolve.Streamline.NSubstitute.UnitTests\Neovolve.Streamline.NSubstitute.UnitTests.csproj", "{2DA40BE0-BD60-4733-949D-F3D9DFBA098C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
5 changes: 5 additions & 0 deletions Neovolve.Streamline.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,10 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=15b5b1f1_002D457c_002D4ca6_002Db278_002D5615aedc07d3/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=4a98fdf6_002D7d98_002D4f5a_002Dafeb_002Dea44ad98c70c/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=8b8504e3_002Df0be_002D4c14_002D9103_002Dc732f2bddc15/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Enum members"&gt;&lt;ElementKinds&gt;&lt;Kind Name="ENUM_MEMBER" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=f9fce829_002De6f4_002D4cb2_002D80f1_002D5497c44f51df/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FCONSTANT/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FVARIABLE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FCONSTRUCTOR/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
Expand Down Expand Up @@ -439,6 +443,7 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EJavaScript_002ECodeStyle_002ESettingsUpgrade_002EJsParsFormattingSettingsUpgrader/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EJavaScript_002ECodeStyle_002ESettingsUpgrade_002EJsWrapperSettingsUpgrader/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/FilterSettingsManager/AttributeFilterXml/@EntryValue">&lt;data /&gt;</s:String>
Expand Down
38 changes: 20 additions & 18 deletions Neovolve.Streamline/TestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public abstract class TestsBase : IDisposable
#endif
{
private readonly Dictionary<string, object?> _services = new();
private readonly object _syncLock = new();
private object? _sut;

/// <summary>
Expand Down Expand Up @@ -118,11 +119,9 @@ public TService Service<TService>(string key)
throw new InvalidOperationException(
"The service has been explicitly stored as null via Use<TService>() and cannot be returned.");
}
else
{
throw new InvalidOperationException(
"The service has been explicitly stored as null via Use<TService>(key) and cannot be returned.");
}

throw new InvalidOperationException(
"The service has been explicitly stored as null via Use<TService>(key) and cannot be returned.");
}

return service;
Expand Down Expand Up @@ -305,23 +304,26 @@ protected virtual ConstructorInfo GetConstructor<T>() where T : class
/// </remarks>
protected T GetSUT<T>() where T : class
{
if (_sut is T sut)
lock (_syncLock)
{
return sut;
}
if (_sut is T sut)
{
return sut;
}

if (_sut != null)
{
// The caller has requested a different type
// Destroy the previous SUT first
DisposeSUT();
}
if (_sut != null)
{
// The caller has requested a different type
// Destroy the previous SUT first
DisposeSUT();
}

// The SUT instance either does not exist or the caller is TestsInternal where a different SUT type is requested
// In this case we will create a new SUT instance
_sut = sut = BuildSUT<T>();
// The SUT instance either does not exist or the caller is TestsInternal where a different SUT type is requested
// In this case we will create a new SUT instance
_sut = sut = BuildSUT<T>();

return sut;
return sut;
}
}

/// <summary>
Expand Down
Loading

0 comments on commit b6bb50d

Please sign in to comment.