Skip to content

Commit c807741

Browse files
Merge pull request #39 from serilog-contrib/feature/shouldly
Support Shouldly
2 parents bf3343f + e78bcad commit c807741

File tree

47 files changed

+1685
-77
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1685
-77
lines changed

.github/workflows/dotnet.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,15 @@ jobs:
6464
run: |
6565
dotnet restore -p:Version=${{ steps.shortversion.outputs.shortversion }}
6666
dotnet test --no-restore -c Release Serilog.Sinks.InMemory.Assertions.Tests.Integration.FluentAssertions8.csproj
67+
68+
- name: Run integration tests for AwesomeAssertions 8
69+
working-directory: test/Serilog.Sinks.InMemory.Assertions.Tests.Integration.AwesomeAssertions8
70+
run: |
71+
dotnet restore -p:Version=${{ steps.shortversion.outputs.shortversion }}
72+
dotnet test --no-restore -c Release Serilog.Sinks.InMemory.Assertions.Tests.Integration.AwesomeAssertions8.csproj
73+
74+
- name: Run integration tests for Shouldly 4.x
75+
working-directory: test/Serilog.Sinks.InMemory.Assertions.Tests.Integration.Shouldly4
76+
run: |
77+
dotnet restore -p:Version=${{ steps.shortversion.outputs.shortversion }}
78+
dotnet test --no-restore -c Release Serilog.Sinks.InMemory.Assertions.Tests.Integration.Shouldly4.csproj

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- Support FluentAssertions version 5, 6, 7 and 8
66
- Support AwesomeAssertions version 8
7+
- Support Shouldly 4.x
78

89
## 0.11.0.0
910

SerilogSinksInMemory.sln

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.InMemory.Awes
5959
EndProject
6060
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serilog.Sinks.InMemory.Assertions.Tests.Unit.AwesomeAssertions8", "test\Serilog.Sinks.InMemory.Assertions.Tests.Unit.AwesomeAssertions8\Serilog.Sinks.InMemory.Assertions.Tests.Unit.AwesomeAssertions8.csproj", "{251A7F28-0166-49BB-AC4C-F72004FE14CC}"
6161
EndProject
62+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serilog.Sinks.InMemory.Assertions.Tests.Unit.Shouldly4", "test\Serilog.Sinks.InMemory.Assertions.Tests.Unit.Shouldly4\Serilog.Sinks.InMemory.Assertions.Tests.Unit.Shouldly4.csproj", "{47696446-67B1-4011-AF4F-AC742A60C7F6}"
63+
EndProject
64+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serilog.Sinks.InMemory.Shouldly4", "src\Serilog.Sinks.InMemory.Shouldly4\Serilog.Sinks.InMemory.Shouldly4.csproj", "{AD9FB993-5657-464E-A3B4-D8163F1AF7C4}"
65+
EndProject
6266
Global
6367
GlobalSection(SolutionConfigurationPlatforms) = preSolution
6468
Debug|Any CPU = Debug|Any CPU
@@ -237,6 +241,30 @@ Global
237241
{251A7F28-0166-49BB-AC4C-F72004FE14CC}.Release|x64.Build.0 = Release|Any CPU
238242
{251A7F28-0166-49BB-AC4C-F72004FE14CC}.Release|x86.ActiveCfg = Release|Any CPU
239243
{251A7F28-0166-49BB-AC4C-F72004FE14CC}.Release|x86.Build.0 = Release|Any CPU
244+
{47696446-67B1-4011-AF4F-AC742A60C7F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
245+
{47696446-67B1-4011-AF4F-AC742A60C7F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
246+
{47696446-67B1-4011-AF4F-AC742A60C7F6}.Debug|x64.ActiveCfg = Debug|Any CPU
247+
{47696446-67B1-4011-AF4F-AC742A60C7F6}.Debug|x64.Build.0 = Debug|Any CPU
248+
{47696446-67B1-4011-AF4F-AC742A60C7F6}.Debug|x86.ActiveCfg = Debug|Any CPU
249+
{47696446-67B1-4011-AF4F-AC742A60C7F6}.Debug|x86.Build.0 = Debug|Any CPU
250+
{47696446-67B1-4011-AF4F-AC742A60C7F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
251+
{47696446-67B1-4011-AF4F-AC742A60C7F6}.Release|Any CPU.Build.0 = Release|Any CPU
252+
{47696446-67B1-4011-AF4F-AC742A60C7F6}.Release|x64.ActiveCfg = Release|Any CPU
253+
{47696446-67B1-4011-AF4F-AC742A60C7F6}.Release|x64.Build.0 = Release|Any CPU
254+
{47696446-67B1-4011-AF4F-AC742A60C7F6}.Release|x86.ActiveCfg = Release|Any CPU
255+
{47696446-67B1-4011-AF4F-AC742A60C7F6}.Release|x86.Build.0 = Release|Any CPU
256+
{AD9FB993-5657-464E-A3B4-D8163F1AF7C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
257+
{AD9FB993-5657-464E-A3B4-D8163F1AF7C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
258+
{AD9FB993-5657-464E-A3B4-D8163F1AF7C4}.Debug|x64.ActiveCfg = Debug|Any CPU
259+
{AD9FB993-5657-464E-A3B4-D8163F1AF7C4}.Debug|x64.Build.0 = Debug|Any CPU
260+
{AD9FB993-5657-464E-A3B4-D8163F1AF7C4}.Debug|x86.ActiveCfg = Debug|Any CPU
261+
{AD9FB993-5657-464E-A3B4-D8163F1AF7C4}.Debug|x86.Build.0 = Debug|Any CPU
262+
{AD9FB993-5657-464E-A3B4-D8163F1AF7C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
263+
{AD9FB993-5657-464E-A3B4-D8163F1AF7C4}.Release|Any CPU.Build.0 = Release|Any CPU
264+
{AD9FB993-5657-464E-A3B4-D8163F1AF7C4}.Release|x64.ActiveCfg = Release|Any CPU
265+
{AD9FB993-5657-464E-A3B4-D8163F1AF7C4}.Release|x64.Build.0 = Release|Any CPU
266+
{AD9FB993-5657-464E-A3B4-D8163F1AF7C4}.Release|x86.ActiveCfg = Release|Any CPU
267+
{AD9FB993-5657-464E-A3B4-D8163F1AF7C4}.Release|x86.Build.0 = Release|Any CPU
240268
EndGlobalSection
241269
GlobalSection(SolutionProperties) = preSolution
242270
HideSolutionNode = FALSE
@@ -257,6 +285,8 @@ Global
257285
{63DF819E-0BF0-40EE-B94B-515BCF8041B9} = {056C07B9-CAD5-4F92-8A24-FBDFDCBA0DDD}
258286
{5136DCA2-E485-4EB1-961C-AD3CC9593946} = {B73801C2-972F-48CD-A47A-87A4544953E2}
259287
{251A7F28-0166-49BB-AC4C-F72004FE14CC} = {056C07B9-CAD5-4F92-8A24-FBDFDCBA0DDD}
288+
{47696446-67B1-4011-AF4F-AC742A60C7F6} = {056C07B9-CAD5-4F92-8A24-FBDFDCBA0DDD}
289+
{AD9FB993-5657-464E-A3B4-D8163F1AF7C4} = {B73801C2-972F-48CD-A47A-87A4544953E2}
260290
EndGlobalSection
261291
GlobalSection(ExtensibilityGlobals) = postSolution
262292
SolutionGuid = {7573ED42-AA91-4C5A-8355-F60D23EC57B1}

src/Serilog.Sinks.InMemory.Assertions.Abstractions/LogEventPropertyValueAssertions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ public interface LogEventPropertyValueAssertions
66
{
77
TValue WhichValue<TValue>();
88
StructuredValueAssertions HavingADestructuredObject(string because = "", params object[] becauseArgs);
9-
AndConstraint<LogEventAssertion> WithValue(object value, string because = "", params object[] becauseArgs);
9+
LogEventAssertion WithValue(object value, string because = "", params object[] becauseArgs);
1010
}

src/Serilog.Sinks.InMemory.Assertions.Abstractions/LogEventsPropertyAssertion.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ namespace Serilog.Sinks.InMemory.Assertions;
44

55
public interface LogEventsPropertyAssertion
66
{
7-
AndConstraint<LogEventsAssertions> WithValues(params object[] values);
7+
LogEventsAssertions WithValues(params object[] values);
88
}

src/Serilog.Sinks.InMemory.Assertions.Abstractions/Serilog.Sinks.InMemory.Assertions.Abstractions.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>netstandard2.0</TargetFramework>
4+
<TargetFramework>netstandard2.1</TargetFramework>
55
<RootNamespace>Serilog.Sinks.InMemory.Assertions</RootNamespace>
66
<IsPackable>false</IsPackable>
77
</PropertyGroup>

src/Serilog.Sinks.InMemory.Assertions/InMemorySinkExtensions.cs

Lines changed: 160 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#nullable enable
22
using System;
3+
using System.Diagnostics;
4+
using System.Diagnostics.CodeAnalysis;
35
using System.IO;
46
using System.Linq;
57
using System.Reflection;
@@ -25,40 +27,69 @@ public static InMemorySinkAssertions Should(this InMemorySink instance)
2527
throw new Exception($"Unable to determine path to load assemblies from");
2628
}
2729

28-
var fluentAssertionsAssembly = AppDomain
29-
.CurrentDomain
30-
.GetAssemblies()
31-
.FirstOrDefault(assembly => assembly.GetName().Name.Equals("FluentAssertions"));
30+
string? adapterName = null;
31+
int? majorVersion = null;
32+
Assembly? versionedAssembly = null;
33+
34+
// Order is important here, first check the loaded assemblies before
35+
// looking on disk because otherwise we might load FluentAssertions from disk
36+
// while Shouldly is already loaded into the AppDomain and that's the one we
37+
// should be using.
38+
// That's also a guess but hey, if you mix and match assertion frameworks you
39+
// can deal with the fall out.
40+
if (IsFluentAssertionsAlreadyLoadedIntoDomain(out var fluentAssertionsAssembly))
41+
{
42+
adapterName = "FluentAssertions";
43+
majorVersion = fluentAssertionsAssembly.GetName().Version.Major;
44+
}
45+
else if (IsAwesomeAssertionsAlreadyLoadedIntoDomain(out var awesomeAssertionsAssembly))
46+
{
47+
adapterName = "AwesomeAssertions";
48+
majorVersion = awesomeAssertionsAssembly.GetName().Version.Major;
49+
}
50+
else if (IsShouldlyAlreadyLoadedIntoDomain(out var shouldlyAssembly))
51+
{
52+
adapterName = "Shouldly";
53+
majorVersion = shouldlyAssembly.GetName().Version.Major;
54+
}
55+
else if (IsFluentAssertionsAvailableOnDisk(assemblyLocation,
56+
out var fluentAssertionsOnDiskAssembly))
57+
{
58+
adapterName = "FluentAssertions";
59+
majorVersion = fluentAssertionsOnDiskAssembly.GetName().Version.Major;
60+
}
61+
else if (IsAwesomeAssertionsAvailableOnDisk(assemblyLocation,
62+
out var awesomeAssertionsOnDiskAssembly))
63+
{
64+
adapterName = "AwesomeAssertions";
65+
majorVersion = awesomeAssertionsOnDiskAssembly.GetName().Version.Major;
66+
}
67+
else if (IsShouldlyAvailableOnDisk(assemblyLocation, out var shouldlyOnDiskAssembly))
68+
{
69+
adapterName = "Shouldly";
70+
majorVersion = shouldlyOnDiskAssembly.GetName().Version.Major;
71+
}
3272

33-
if (fluentAssertionsAssembly == null)
73+
if (adapterName != null && majorVersion != null)
3474
{
35-
var fluentAssertionsAssemblyPath = Path.Combine(assemblyLocation, "FluentAssertions.dll");
75+
var versionedLocation = Path.Combine(
76+
assemblyLocation,
77+
$"Serilog.Sinks.InMemory.{adapterName}{majorVersion}.dll");
3678

37-
try
79+
if (!File.Exists(versionedLocation))
3880
{
39-
fluentAssertionsAssembly = Assembly.LoadFile(fluentAssertionsAssemblyPath);
40-
}
41-
catch (FileNotFoundException e)
42-
{
43-
throw new Exception($"Could not find assembly '{fluentAssertionsAssemblyPath}'", e);
81+
throw new InvalidOperationException($"Detected {adapterName} version {majorVersion} but the assertions adapter wasn't found on disk");
4482
}
83+
84+
versionedAssembly = Assembly.LoadFile(versionedLocation);
4585
}
4686

47-
var assertionLibrary = IsAwesomeAssertions(fluentAssertionsAssembly)
48-
? "AwesomeAssertions"
49-
: "FluentAssertions";
50-
51-
var fluentAssertionsMajorVersion = fluentAssertionsAssembly.GetName().Version.Major;
52-
53-
var versionedLocation = Path.Combine(
54-
assemblyLocation,
55-
$"Serilog.Sinks.InMemory.{assertionLibrary}{fluentAssertionsMajorVersion}.dll");
56-
57-
var versionedAssembly = Assembly.LoadFile(versionedLocation);
58-
59-
_assertionsType = versionedAssembly
60-
.GetTypes()
61-
.SingleOrDefault(t => t.Name == "InMemorySinkAssertionsImpl");
87+
if (versionedAssembly != null)
88+
{
89+
_assertionsType = versionedAssembly
90+
.GetTypes()
91+
.SingleOrDefault(t => t.Name == "InMemorySinkAssertionsImpl");
92+
}
6293
}
6394
}
6495

@@ -68,17 +99,114 @@ public static InMemorySinkAssertions Should(this InMemorySink instance)
6899
}
69100

70101
var snapshotInstance = SnapshotOf(instance);
71-
102+
72103
return (InMemorySinkAssertions)Activator.CreateInstance(
73104
_assertionsType, snapshotInstance);
74105
}
75106

76-
private static bool IsAwesomeAssertions(Assembly fluentAssertionsAssembly)
107+
private static bool IsFluentAssertionsAlreadyLoadedIntoDomain(
108+
[NotNullWhen(true)] out Assembly? fluentAssertionsAssembly)
109+
{
110+
fluentAssertionsAssembly = AppDomain
111+
.CurrentDomain
112+
.GetAssemblies()
113+
.FirstOrDefault(assembly =>
114+
{
115+
if (assembly.GetName().Name.Equals("FluentAssertions"))
116+
{
117+
var metadataAttributes = assembly.GetCustomAttributes<AssemblyMetadataAttribute>().ToArray();
118+
119+
return !metadataAttributes.Any() ||
120+
metadataAttributes.Any(metadata => metadata.Value.Contains("FluentAssertions", StringComparison.OrdinalIgnoreCase));
121+
}
122+
123+
return false;
124+
});
125+
126+
return fluentAssertionsAssembly != null;
127+
}
128+
129+
private static bool IsAwesomeAssertionsAlreadyLoadedIntoDomain(
130+
[NotNullWhen(true)] out Assembly? awesomeAssertionsAssembly)
77131
{
78-
var assemblyMetadata = fluentAssertionsAssembly.GetCustomAttributes<AssemblyMetadataAttribute>();
132+
awesomeAssertionsAssembly = AppDomain
133+
.CurrentDomain
134+
.GetAssemblies()
135+
.FirstOrDefault(assembly =>
136+
assembly.GetName().Name.Equals("FluentAssertions") &&
137+
assembly.GetCustomAttributes<AssemblyMetadataAttribute>()
138+
.Any(metadata => metadata.Value.Contains("AwesomeAssertions", StringComparison.OrdinalIgnoreCase)));
139+
140+
return awesomeAssertionsAssembly != null;
141+
}
142+
143+
private static bool IsShouldlyAlreadyLoadedIntoDomain([NotNullWhen(true)] out Assembly? shouldlyAssembly)
144+
{
145+
shouldlyAssembly = AppDomain
146+
.CurrentDomain
147+
.GetAssemblies()
148+
.FirstOrDefault(assembly => assembly.GetName().Name.Equals("Shouldly"));
149+
150+
return shouldlyAssembly != null;
151+
}
152+
153+
private static bool IsFluentAssertionsAvailableOnDisk(
154+
string assemblyLocation,
155+
[NotNullWhen(true)] out Assembly? assembly)
156+
{
157+
var assemblyPath = Path.Combine(assemblyLocation, "FluentAssertions.dll");
158+
159+
if (File.Exists(assemblyPath))
160+
{
161+
assembly = Assembly.LoadFile(assemblyPath);
162+
163+
var metadataAttributes = assembly.GetCustomAttributes<AssemblyMetadataAttribute>().ToList();
164+
165+
if(!metadataAttributes.Any() || metadataAttributes.Any(metadata => metadata.Value.Contains("FluentAssertions", StringComparison.OrdinalIgnoreCase)))
166+
{
167+
return true;
168+
}
169+
}
170+
171+
assembly = null;
172+
return false;
173+
}
174+
175+
private static bool IsAwesomeAssertionsAvailableOnDisk(
176+
string assemblyLocation,
177+
[NotNullWhen(true)] out Assembly? assembly)
178+
{
179+
var assemblyPath = Path.Combine(assemblyLocation, "FluentAssertions.dll");
180+
181+
if (File.Exists(assemblyPath))
182+
{
183+
assembly = Assembly.LoadFile(assemblyPath);
184+
185+
if (assembly.GetCustomAttributes<AssemblyMetadataAttribute>()
186+
.Any(metadata => metadata.Value.Contains("AwesomeAssertions", StringComparison.OrdinalIgnoreCase)))
187+
{
188+
return true;
189+
}
190+
}
191+
192+
assembly = null;
193+
return false;
194+
}
195+
196+
private static bool IsShouldlyAvailableOnDisk(
197+
string assemblyLocation,
198+
[NotNullWhen(true)] out Assembly? assembly)
199+
{
200+
var assemblyPath = Path.Combine(assemblyLocation, "Shouldly.dll");
201+
202+
if (File.Exists(assemblyPath))
203+
{
204+
assembly = Assembly.LoadFile(assemblyPath);
205+
return true;
206+
}
79207

80-
// Yuck yuck yuck
81-
return assemblyMetadata.Any(metadata => metadata.Value.Contains("AwesomeAssertions"));
208+
assembly = null;
209+
return false;
82210
}
83211

84212
/*

src/Serilog.Sinks.InMemory.Assertions/Serilog.Sinks.InMemory.Assertions.csproj

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>netstandard2.0</TargetFramework>
4+
<TargetFramework>netstandard2.1</TargetFramework>
55
</PropertyGroup>
66

77
<PropertyGroup>
@@ -44,7 +44,7 @@
4444
<ReferenceOutputAssembly>True</ReferenceOutputAssembly>
4545
<Package>true</Package> <!-- This ensures it ends up in the NuGet package -->
4646
<OutputItemType>Content</OutputItemType> <!-- Pretend this is a content item -->
47-
<PackagePath>lib/netstandard2.0</PackagePath> <!-- This controls the location in the package, without it this file would end up in content/ -->
47+
<PackagePath>lib/netstandard2.1</PackagePath> <!-- This controls the location in the package, without it this file would end up in content/ -->
4848
<ExcludeAssets>all</ExcludeAssets> <!-- This ensures that this reference isn't treated as a transitive dependency -->
4949
</ProjectReference>
5050

@@ -58,35 +58,42 @@
5858
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <!-- This ensures the assembly is copied -->
5959
<Package>true</Package> <!-- This ensures it ends up in the NuGet package -->
6060
<OutputItemType>Content</OutputItemType> <!-- Pretend this is a content item -->
61-
<PackagePath>lib/netstandard2.0</PackagePath> <!-- This controls the location in the package, without it this file would end up in content/ -->
61+
<PackagePath>lib/netstandard2.1</PackagePath> <!-- This controls the location in the package, without it this file would end up in content/ -->
6262
</ProjectReference>
6363
<ProjectReference Include="..\Serilog.Sinks.InMemory.FluentAssertions6\Serilog.Sinks.InMemory.FluentAssertions6.csproj">
6464
<ReferenceOutputAssembly>False</ReferenceOutputAssembly>
6565
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
6666
<Package>true</Package>
6767
<OutputItemType>Content</OutputItemType>
68-
<PackagePath>lib/netstandard2.0</PackagePath>
68+
<PackagePath>lib/netstandard2.1</PackagePath>
6969
</ProjectReference>
7070
<ProjectReference Include="..\Serilog.Sinks.InMemory.FluentAssertions7\Serilog.Sinks.InMemory.FluentAssertions7.csproj">
7171
<ReferenceOutputAssembly>False</ReferenceOutputAssembly>
7272
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
7373
<Package>true</Package>
7474
<OutputItemType>Content</OutputItemType>
75-
<PackagePath>lib/netstandard2.0</PackagePath>
75+
<PackagePath>lib/netstandard2.1</PackagePath>
7676
</ProjectReference>
7777
<ProjectReference Include="..\Serilog.Sinks.InMemory.FluentAssertions8\Serilog.Sinks.InMemory.FluentAssertions8.csproj">
7878
<ReferenceOutputAssembly>False</ReferenceOutputAssembly>
7979
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
8080
<Package>true</Package>
8181
<OutputItemType>Content</OutputItemType>
82-
<PackagePath>lib/netstandard2.0</PackagePath>
82+
<PackagePath>lib/netstandard2.1</PackagePath>
8383
</ProjectReference>
8484
<ProjectReference Include="..\Serilog.Sinks.InMemory.AwesomeAssertions8\Serilog.Sinks.InMemory.AwesomeAssertions8.csproj">
8585
<ReferenceOutputAssembly>False</ReferenceOutputAssembly>
8686
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
8787
<Package>true</Package>
8888
<OutputItemType>Content</OutputItemType>
89-
<PackagePath>lib/netstandard2.0</PackagePath>
89+
<PackagePath>lib/netstandard2.1</PackagePath>
90+
</ProjectReference>
91+
<ProjectReference Include="..\Serilog.Sinks.InMemory.Shouldly4\Serilog.Sinks.InMemory.Shouldly4.csproj">
92+
<ReferenceOutputAssembly>False</ReferenceOutputAssembly>
93+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
94+
<Package>true</Package>
95+
<OutputItemType>Content</OutputItemType>
96+
<PackagePath>lib/netstandard2.1</PackagePath>
9097
</ProjectReference>
9198
</ItemGroup>
9299
</Project>

0 commit comments

Comments
 (0)