Skip to content

Commit 92688be

Browse files
authored
Fix bug with SendExtAcknowledgedDataAsync (#40)
* Update project files to target .NET 9 and upgrade packages Updated various project files to target .NET 9.0 frameworks, including net9.0, net9.0-android, net9.0-windows10.0.26100.0, and net9.0-windows10.0.19041.0. Upgraded package versions across multiple projects, including Microsoft.Extensions.Hosting, Microsoft.Maui.Core, CommunityToolkit.Mvvm, Serilog.Extensions.Hosting, Serilog.Extensions.Logging, Google.Protobuf, Grpc.Net.Client, Grpc.Tools, and others. Replaced MSTest with xUnit in unit test projects. * Migrate unit tests from MSTest to xUnit The test framework has been switched from MSTest to xUnit. This includes: - Replacing `[TestClass]` with the class declaration. - Replacing `[TestInitialize]` with the class constructor. - Replacing `[TestMethod]` with `[Fact]` and `[DataRow]` with `[Theory]` and `[InlineData]`. - Updating `Assert` methods to xUnit equivalents. - Changing `Mock` objects to `readonly` fields. - Simplifying array initializations using array literals. - Updating `using` directives to include xUnit. * Refactor array instantiation in SendMessageChannelTests Updated array instantiation to use Array.Empty<T>() instead of new T[0] or new object[] {}. This change affects various method calls and test setups, including Dispose, SendAcknowledgedData, SendAcknowledgedDataAsync, SendBroadcastData, SendBurstTransfer, SendBurstTransferAsync, SendExtAcknowledgedData, SendExtBroadcastData, SendExtBurstTransfer, and SendExtBurstTransferAsync. Also updated the data initialization and method invocation in SendExtAcknowledgedDataAsync_InvokesMethodMultipleTimesAndReturnsPass. * Replace Frame with Border elements and update bindings Replaced Frame elements with Border elements across multiple XAML files, updating properties such as BorderColor to Stroke, and adding Margin, Padding, and StrokeShape properties. Updated bindings to include x:DataType=RadioButton and x:DataType=Entry where applicable. Added event handlers and updated command parameter bindings in HomePage.xaml and HomePage.xaml.cs. Updated byte array initialization syntax in BicyclePowerViewModel.cs. * Improve logging in SendMessageChannel and MauiProgram Updated log message formatting for consistency in `SendMessageChannel.cs` and added logging for timeout results. Enhanced logging configuration in `MauiProgram.cs` to set minimum level to Debug in DEBUG mode. * Workaround to build the installer package. * Refactor logging in SendMessageChannel and MauiProgram Updated logging in SendMessageChannel.cs to remove Task ID from debug messages, focusing on busy flags and channel index. Added Serilog and its extensions to MauiAntGrpcClient.csproj for enhanced logging capabilities. Replaced the existing logging framework with Serilog in MauiProgram.cs, initializing it early and modifying the debug logging configuration to utilize Serilog. * Update package versions and release notes - Incremented assembly versions for Hosting (1.1.2.0) and AntPlus (5.0.1.0). - Updated package release notes to reflect bug fixes related to ANT radio hardware. - Added new version entries in AntPlus and Hosting Extensions version history files. - Updated ContentLayout to include new version 5.0.1.0 and change visibility for version 3.1.0.0. - Modified Documentation.shfbproj to reference new versions. - Created new XML files for version 5.0.1.0 and 1.1.2.0 with detailed release notes.
1 parent acf519c commit 92688be

File tree

76 files changed

+1628
-1361
lines changed

Some content is hidden

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

76 files changed

+1628
-1361
lines changed

AntPlus.Extensions.Hosting/Hosting.csproj

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,19 @@
1616
<EmbedUntrackedSources>true</EmbedUntrackedSources>
1717
<PackageIcon>PackageLogo.png</PackageIcon>
1818
<PackageReadmeFile>readme.md</PackageReadmeFile>
19-
<PackageReleaseNotes>Fixed potential race condition sending multiple messages to multiple ANT devices managed by the AntCollection class.</PackageReleaseNotes>
19+
<PackageReleaseNotes>Bugfix: Issue #39. Clears msssage in ANT radio hardware when SendExtAcknowledgedDataAsync timesout.</PackageReleaseNotes>
2020
<PackageLicenseExpression>MIT</PackageLicenseExpression>
2121
<IncludeSymbols>True</IncludeSymbols>
2222
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
23-
<AssemblyVersion>1.1.1.0</AssemblyVersion>
23+
<AssemblyVersion>1.1.2.0</AssemblyVersion>
2424
<Version>$(VersionPrefix)$(AssemblyVersion)</Version>
2525
<PackageProjectUrl>https://stephenhidem.github.io/AntPlus</PackageProjectUrl>
2626
<RepositoryUrl>https://github.com/StephenHidem/AntPlus</RepositoryUrl>
2727
</PropertyGroup>
2828

2929
<ItemGroup>
30-
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
31-
<PackageReference Include="Microsoft.Maui.Core" Version="8.0.91" />
30+
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.2" />
31+
<PackageReference Include="Microsoft.Maui.Core" Version="9.0.40" />
3232
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0">
3333
<PrivateAssets>all</PrivateAssets>
3434
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

AntPlus.Extensions.Hosting/SendMessageChannel.cs

+11-3
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,20 @@ public Task<MessagingReturnCode> SendExtAcknowledgedDataAsync(ChannelId channelI
8585
.ContinueWith(antecedent =>
8686
{
8787
int index = antecedent.Result;
88-
_logger.LogDebug("SendExtAcknowledgedDataAsync: channel index = {ChannelIndex}, channel ID = 0x{ChannelId:X8}, data = {Data}", index, channelId.Id, BitConverter.ToString(data));
88+
_logger.LogDebug("SendExtAcknowledgedDataAsync: Channel index = {ChannelIndex}, channel ID = 0x{ChannelId:X8}, data = {Data}", index, channelId.Id, BitConverter.ToString(data));
8989
_channels[index].SendExtAcknowledgedDataAsync(channelId, data, ackWaitTime)
9090
.ContinueWith(innerAntecedent =>
9191
{
92+
_logger.LogDebug("SendExtAcknowledgedDataAsync: Channel index = {ChannelIndex}, channel ID = 0x{ChannelId:X8}, result = {Result}", index, channelId.Id, innerAntecedent.Result);
9293
lock (_channelLock)
9394
{
95+
// unassign then assign the channel to reset the channel if the result is timeout
96+
if (innerAntecedent.Result == MessagingReturnCode.Timeout)
97+
{
98+
_channels[index].UnassignChannel(ackWaitTime);
99+
_channels[index].AssignChannel(ChannelType.BaseSlaveReceive, 0, ackWaitTime);
100+
}
101+
94102
// release the channel and notify this channel is available
95103
_busyFlags[index] = false;
96104
Monitor.Pulse(_channelLock);
@@ -120,10 +128,10 @@ private Task<int> GetAvailableChannelIndexAsync()
120128
// find an available channel
121129
while ((i = Array.FindIndex(_busyFlags, flag => !flag)) == -1)
122130
{
123-
_logger.LogDebug("GetAvailableChannelIndexAsync: Task ID = {TaskId}, all channels are busy", Task.CurrentId);
131+
_logger.LogDebug("GetAvailableChannelIndexAsync: All channels are busy");
124132
Monitor.Wait(_channelLock);
125133
}
126-
_logger.LogDebug("GetAvailableChannelIndexAsync: Task ID = {TaskId}, _busyFlags = {BusyFlags}, channel index = {ChannelIndex}", Task.CurrentId, _busyFlags, i);
134+
_logger.LogDebug("GetAvailableChannelIndexAsync: _busyFlags = {BusyFlags}, channel index = {ChannelIndex}", _busyFlags, i);
127135
_busyFlags[i] = true;
128136
}
129137
return i;

AntPlus.UnitTests/AntDeviceCollectionTests.cs

+32-33
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,20 @@
1111
using System.Linq;
1212
using System.Threading;
1313
using System.Threading.Tasks;
14+
using Xunit;
1415

1516
namespace AntPlus.UnitTests
1617
{
17-
[TestClass]
1818
public class AntDeviceCollectionTests
1919
{
20-
private MockRepository mockRepository;
20+
private readonly MockRepository mockRepository;
2121

22-
private Mock<IAntRadio> mockAntRadio;
23-
private Mock<IAntChannel> mockAntChannel;
24-
private Mock<ILogger> mockLogger;
25-
private Mock<ILoggerFactory> mockLoggerFactory;
22+
private readonly Mock<IAntRadio> mockAntRadio;
23+
private readonly Mock<IAntChannel> mockAntChannel;
24+
private readonly Mock<ILogger> mockLogger;
25+
private readonly Mock<ILoggerFactory> mockLoggerFactory;
2626

27-
[TestInitialize]
28-
public void TestInitialize()
27+
public AntDeviceCollectionTests()
2928
{
3029
mockRepository = new MockRepository(MockBehavior.Loose);
3130

@@ -47,7 +46,7 @@ private AntDeviceCollection CreateAntDeviceCollection()
4746
return adc;
4847
}
4948

50-
[TestMethod]
49+
[Fact]
5150
public async Task MultithreadedAdd_Collection_ExpectedCount()
5251
{
5352
// Arrange
@@ -69,14 +68,14 @@ public async Task MultithreadedAdd_Collection_ExpectedCount()
6968

7069
// Act
7170
semaphore.Release(numberOfDevices);
72-
Task.WaitAll(tasks);
71+
await Task.WhenAll(tasks);
7372

7473
// Assert
75-
Assert.AreEqual(numberOfDevices, antDeviceCollection.Count);
74+
Assert.Equal(numberOfDevices, antDeviceCollection.Count);
7675
mockRepository.VerifyAll();
7776
}
7877

79-
[TestMethod]
78+
[Fact]
8079
public async Task MultithreadedRemove_Collection_ExpectedCount()
8180
{
8281
// Arrange
@@ -99,28 +98,28 @@ public async Task MultithreadedRemove_Collection_ExpectedCount()
9998

10099
// Act
101100
semaphore.Release(numberOfDevices);
102-
Task.WaitAll(tasks);
101+
await Task.WhenAll(tasks);
103102

104103
// Assert
105-
Assert.AreEqual(0, antDeviceCollection.Count);
104+
Assert.Empty(antDeviceCollection);
106105
mockRepository.VerifyAll();
107106
}
108107

109-
[TestMethod]
110-
[DataRow(HeartRate.DeviceClass, typeof(HeartRate))]
111-
[DataRow(BicyclePower.DeviceClass, typeof(StandardPowerSensor))]
112-
[DataRow(BikeSpeedSensor.DeviceClass, typeof(BikeSpeedSensor))]
113-
[DataRow(BikeCadenceSensor.DeviceClass, typeof(BikeCadenceSensor))]
114-
[DataRow(CombinedSpeedAndCadenceSensor.DeviceClass, typeof(CombinedSpeedAndCadenceSensor))]
115-
[DataRow(FitnessEquipment.DeviceClass, typeof(UnknownDevice))]
116-
[DataRow(MuscleOxygen.DeviceClass, typeof(MuscleOxygen))]
117-
[DataRow(Geocache.DeviceClass, typeof(Geocache))]
118-
[DataRow(Tracker.DeviceClass, typeof(Tracker))]
119-
[DataRow(StrideBasedSpeedAndDistance.DeviceClass, typeof(StrideBasedSpeedAndDistance))]
108+
[Theory]
109+
[InlineData(HeartRate.DeviceClass, typeof(HeartRate))]
110+
[InlineData(BicyclePower.DeviceClass, typeof(StandardPowerSensor))]
111+
[InlineData(BikeSpeedSensor.DeviceClass, typeof(BikeSpeedSensor))]
112+
[InlineData(BikeCadenceSensor.DeviceClass, typeof(BikeCadenceSensor))]
113+
[InlineData(CombinedSpeedAndCadenceSensor.DeviceClass, typeof(CombinedSpeedAndCadenceSensor))]
114+
[InlineData(FitnessEquipment.DeviceClass, typeof(UnknownDevice))]
115+
[InlineData(MuscleOxygen.DeviceClass, typeof(MuscleOxygen))]
116+
[InlineData(Geocache.DeviceClass, typeof(Geocache))]
117+
[InlineData(Tracker.DeviceClass, typeof(Tracker))]
118+
[InlineData(StrideBasedSpeedAndDistance.DeviceClass, typeof(StrideBasedSpeedAndDistance))]
120119
public async Task ChannelResponseEvent_Collection_ExpectedDeviceInCollection(byte deviceClass, Type deviceType)
121120
{
122121
// Arrange
123-
byte[] id = new byte[4] { 1, 0, deviceClass, 0 };
122+
byte[] id = [1, 0, deviceClass, 0];
124123
ChannelId cid = new(BitConverter.ToUInt32(id));
125124
mockAntChannel.SetupAdd(m => m.ChannelResponse += It.IsAny<EventHandler<AntResponse>>());
126125
mockAntChannel.SetupRemove(m => m.ChannelResponse -= It.IsAny<EventHandler<AntResponse>>());
@@ -131,8 +130,8 @@ public async Task ChannelResponseEvent_Collection_ExpectedDeviceInCollection(byt
131130
// Act
132131
mockAntChannel.Raise(m => m.ChannelResponse += null, mockAntChannel.Object, mockResponse);
133132

134-
Assert.AreEqual(1, antDeviceCollection.Count);
135-
Assert.AreEqual(deviceType, antDeviceCollection[0].GetType());
133+
Assert.Single(antDeviceCollection);
134+
Assert.Equal(deviceType, antDeviceCollection[0].GetType());
136135
}
137136

138137
class MockResponse : AntResponse
@@ -144,7 +143,7 @@ public MockResponse(ChannelId channelId, byte[] payload)
144143
}
145144
}
146145

147-
//[TestMethod]
146+
//[Fact]
148147
//public async Task DeviceOffline_RemovesDeviceFromCollection()
149148
//{
150149
// // Arrange
@@ -158,10 +157,10 @@ public MockResponse(ChannelId channelId, byte[] payload)
158157
// //Thread.Sleep(100);
159158

160159
// // Assert
161-
// Assert.AreEqual(0, antDeviceCollection.Count);
160+
// Assert.Equal(0, antDeviceCollection.Count);
162161
//}
163162

164-
[TestMethod]
163+
[Fact]
165164
public async Task MessageHandler_NullChannelId_LogsCritical()
166165
{
167166
// Arrange
@@ -183,7 +182,7 @@ public async Task MessageHandler_NullChannelId_LogsCritical()
183182
Times.Once);
184183
}
185184

186-
[TestMethod]
185+
[Fact]
187186
public async Task MessageHandler_NullPayload_LogsCritical()
188187
{
189188
// Arrange
@@ -205,7 +204,7 @@ public async Task MessageHandler_NullPayload_LogsCritical()
205204
Times.Once);
206205
}
207206

208-
//[TestMethod]
207+
//[Fact]
209208
//public void CreateAntDevice_ValidChannelId_CreatesCorrectDevice()
210209
//{
211210
// // Arrange

AntPlus.UnitTests/AntDeviceTests.cs

+34-35
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,28 @@
44
using SmallEarthTech.AntPlus.DeviceProfiles.AssetTracker;
55
using SmallEarthTech.AntRadioInterface;
66
using System.Threading;
7+
using Xunit;
78

89
namespace AntPlus.UnitTests
910
{
10-
[TestClass]
1111
public class AntDeviceTests
1212
{
13-
private MockRepository mockRepository;
13+
private readonly MockRepository mockRepository;
1414

15-
private Mock<IAntChannel> mockAntChannel;
16-
private Mock<ILogger<Tracker>> mockLogger;
15+
private readonly Mock<IAntChannel> mockAntChannel;
16+
private readonly Mock<ILogger<Tracker>> mockLogger;
1717

18-
[TestInitialize]
19-
public void TestInitialize()
18+
public AntDeviceTests()
2019
{
2120
mockRepository = new MockRepository(MockBehavior.Strict);
2221

2322
mockAntChannel = mockRepository.Create<IAntChannel>();
2423
mockLogger = mockRepository.Create<ILogger<Tracker>>(MockBehavior.Loose);
2524
}
2625

27-
[TestMethod]
28-
[DataRow((uint)0x0FFF0001, (uint)0x00000001)]
29-
[DataRow((uint)0xFFFF0001, (uint)0x000F0001)]
26+
[Theory]
27+
[InlineData((uint)0x0FFF0001, (uint)0x00000001)]
28+
[InlineData((uint)0xFFFF0001, (uint)0x000F0001)]
3029
public void Ctor_ChannelID_VerifyDeviceNumber(uint channelId, uint expectedDeviceNumber)
3130
{
3231
// Arrange
@@ -36,12 +35,12 @@ public void Ctor_ChannelID_VerifyDeviceNumber(uint channelId, uint expectedDevic
3635
Mock<AntDevice> antDevice = new(cid, mockAntChannel.Object, mockLogger.Object, (int)500);
3736

3837
// Assert
39-
Assert.AreEqual((uint)expectedDeviceNumber, antDevice.Object.ChannelId.DeviceNumber);
38+
Assert.Equal((uint)expectedDeviceNumber, antDevice.Object.ChannelId.DeviceNumber);
4039
}
4140

42-
[TestMethod]
43-
[DataRow((uint)0xFF81FFFF, (byte)0x01)]
44-
[DataRow((uint)0xFFFFFFFF, (byte)0x7F)]
41+
[Theory]
42+
[InlineData(0xFF81FFFF, (byte)0x01)]
43+
[InlineData(0xFFFFFFFF, (byte)0x7F)]
4544
public void Ctor_ChannelID_VerifyDeviceType(uint channelId, byte expectedDeviceType)
4645
{
4746
// Arrange
@@ -51,12 +50,12 @@ public void Ctor_ChannelID_VerifyDeviceType(uint channelId, byte expectedDeviceT
5150
Mock<AntDevice> antDevice = new(cid, mockAntChannel.Object, mockLogger.Object, (int)500);
5251

5352
// Assert
54-
Assert.AreEqual(expectedDeviceType, antDevice.Object.ChannelId.DeviceType);
53+
Assert.Equal(expectedDeviceType, antDevice.Object.ChannelId.DeviceType);
5554
}
5655

57-
[TestMethod]
58-
[DataRow((uint)0x00800000, true)]
59-
[DataRow((uint)0xFF7FFFFF, false)]
56+
[Theory]
57+
[InlineData((uint)0x00800000, true)]
58+
[InlineData((uint)0xFF7FFFFF, false)]
6059
public void Ctor_ChannelID_VerifyPairingBit(uint channelId, bool expectedResult)
6160
{
6261
// Arrange
@@ -66,12 +65,12 @@ public void Ctor_ChannelID_VerifyPairingBit(uint channelId, bool expectedResult)
6665
Mock<AntDevice> antDevice = new(cid, mockAntChannel.Object, mockLogger.Object, (int)500);
6766

6867
// Assert
69-
Assert.AreEqual(expectedResult, antDevice.Object.ChannelId.IsPairingBitSet);
68+
Assert.Equal(expectedResult, antDevice.Object.ChannelId.IsPairingBitSet);
7069
}
7170

72-
[TestMethod]
73-
[DataRow((uint)0x04000000, true)]
74-
[DataRow((uint)0xFBFFFFFF, false)]
71+
[Theory]
72+
[InlineData((uint)0x04000000, true)]
73+
[InlineData((uint)0xFBFFFFFF, false)]
7574
public void Ctor_ChannelID_VerifyAreGlobalPageUsed(uint channelId, bool expectedResult)
7675
{
7776
// Arrange
@@ -81,18 +80,18 @@ public void Ctor_ChannelID_VerifyAreGlobalPageUsed(uint channelId, bool expected
8180
Mock<AntDevice> antDevice = new(cid, mockAntChannel.Object, mockLogger.Object, (int)500);
8281

8382
// Assert
84-
Assert.AreEqual(expectedResult, antDevice.Object.ChannelId.AreGlobalDataPagesUsed);
83+
Assert.Equal(expectedResult, antDevice.Object.ChannelId.AreGlobalDataPagesUsed);
8584
}
8685

87-
[TestMethod]
88-
[DataRow((uint)0xF0FFFFFF, ChannelSharing.Reserved)]
89-
[DataRow((uint)0xFCFFFFFF, ChannelSharing.Reserved)]
90-
[DataRow((uint)0xF1FFFFFF, ChannelSharing.IndependentChannel)]
91-
[DataRow((uint)0xFDFFFFFF, ChannelSharing.IndependentChannel)]
92-
[DataRow((uint)0xF2FFFFFF, ChannelSharing.SharedChannelOneByteAddress)]
93-
[DataRow((uint)0xFEFFFFFF, ChannelSharing.SharedChannelOneByteAddress)]
94-
[DataRow((uint)0xF3FFFFFF, ChannelSharing.SharedChannelTwoByteAddress)]
95-
[DataRow((uint)0xFFFFFFFF, ChannelSharing.SharedChannelTwoByteAddress)]
86+
[Theory]
87+
[InlineData((uint)0xF0FFFFFF, ChannelSharing.Reserved)]
88+
[InlineData((uint)0xFCFFFFFF, ChannelSharing.Reserved)]
89+
[InlineData((uint)0xF1FFFFFF, ChannelSharing.IndependentChannel)]
90+
[InlineData((uint)0xFDFFFFFF, ChannelSharing.IndependentChannel)]
91+
[InlineData((uint)0xF2FFFFFF, ChannelSharing.SharedChannelOneByteAddress)]
92+
[InlineData((uint)0xFEFFFFFF, ChannelSharing.SharedChannelOneByteAddress)]
93+
[InlineData((uint)0xF3FFFFFF, ChannelSharing.SharedChannelTwoByteAddress)]
94+
[InlineData((uint)0xFFFFFFFF, ChannelSharing.SharedChannelTwoByteAddress)]
9695
public void Ctor_ChannelID_VerifyTransmissionType(uint channelId, ChannelSharing expectedTransmissionType)
9796
{
9897
// Arrange
@@ -102,10 +101,10 @@ public void Ctor_ChannelID_VerifyTransmissionType(uint channelId, ChannelSharing
102101
Mock<AntDevice> antDevice = new(cid, mockAntChannel.Object, mockLogger.Object, (int)500);
103102

104103
// Assert
105-
Assert.AreEqual(expectedTransmissionType, antDevice.Object.ChannelId.TransmissionType);
104+
Assert.Equal(expectedTransmissionType, antDevice.Object.ChannelId.TransmissionType);
106105
}
107106

108-
[TestMethod]
107+
[Fact]
109108
public void Timeout_Offline_ExpectedEventAndStatus()
110109
{
111110
// Arrange
@@ -118,8 +117,8 @@ public void Timeout_Offline_ExpectedEventAndStatus()
118117
Thread.Sleep(100);
119118

120119
// Assert
121-
Assert.IsTrue(offline == true);
122-
Assert.IsTrue(antDevice.Object.Offline == true);
120+
Assert.True(offline, "Event test");
121+
Assert.True(antDevice.Object.Offline, "Device offline status");
123122
}
124123
}
125124
}

AntPlus.UnitTests/AntPlus.UnitTests.csproj

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net6.0</TargetFramework>
4+
<TargetFramework>net9.0</TargetFramework>
55
<Nullable>disable</Nullable>
66

77
<IsPackable>false</IsPackable>
@@ -10,12 +10,15 @@
1010
<ItemGroup>
1111
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
1212
<PackageReference Include="Moq" Version="4.20.72" />
13-
<PackageReference Include="MSTest.TestAdapter" Version="3.7.3" />
14-
<PackageReference Include="MSTest.TestFramework" Version="3.7.3" />
1513
<PackageReference Include="coverlet.collector" Version="6.0.4">
1614
<PrivateAssets>all</PrivateAssets>
1715
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1816
</PackageReference>
17+
<PackageReference Include="xunit" Version="2.9.3" />
18+
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
19+
<PrivateAssets>all</PrivateAssets>
20+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
21+
</PackageReference>
1922
</ItemGroup>
2023

2124
<ItemGroup>

0 commit comments

Comments
 (0)