Skip to content

Commit 318e330

Browse files
authored
Merge pull request #83 from TRUMPF-IoT/add-retry-for-file-chunks
Add retry for file chunks
2 parents d09b592 + 748c97e commit 318e330

10 files changed

+216
-66
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ tools/
2525
# Visual Studio 2015 cache/options directory
2626
.vs/
2727
.vscode/
28+
.idea/
2829

2930
# MSTest test Results
3031
[Tt]est[Rr]esult*/

.reuse/dep5

-46
This file was deleted.

Directory.Packages.props

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ SPDX-License-Identifier: MPL-2.0
1212
<PackageVersion Include="C-DEngine" Version="6.104.0" />
1313
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
1414
<PackageVersion Include="LiteDB" Version="5.0.21" />
15+
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="9.0.0" />
1516
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
1617
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.0" />
1718
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.0" />

REUSE.toml

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
version = 1
2+
SPDX-PackageName = "Smart Application Framework"
3+
SPDX-PackageSupplier = "TRUMPF Laser GmbH <[email protected]>"
4+
SPDX-PackageDownloadLocation = "https://github.com/TRUMPF-IoT/saf"
5+
6+
[[annotations]]
7+
path = ".github/**/**"
8+
precedence = "aggregate"
9+
SPDX-FileCopyrightText = "2017-2021 TRUMPF Laser GmbH"
10+
SPDX-License-Identifier = "MPL-2.0"
11+
12+
[[annotations]]
13+
path = "**.md"
14+
precedence = "aggregate"
15+
SPDX-FileCopyrightText = "2017-2021 TRUMPF Laser GmbH"
16+
SPDX-License-Identifier = "MPL-2.0"
17+
18+
[[annotations]]
19+
path = "docs/_config.yml"
20+
precedence = "aggregate"
21+
SPDX-FileCopyrightText = "2017-2021 TRUMPF Laser GmbH"
22+
SPDX-License-Identifier = "MPL-2.0"
23+
24+
[[annotations]]
25+
path = "docs/**/**"
26+
precedence = "aggregate"
27+
SPDX-FileCopyrightText = "2017-2021 TRUMPF Laser GmbH"
28+
SPDX-License-Identifier = "MPL-2.0"
29+
30+
[[annotations]]
31+
path = "src/**/**.txt"
32+
precedence = "aggregate"
33+
SPDX-FileCopyrightText = "2017-2021 TRUMPF Laser GmbH"
34+
SPDX-License-Identifier = "MPL-2.0"
35+
36+
[[annotations]]
37+
path = "src/**.sln"
38+
precedence = "aggregate"
39+
SPDX-FileCopyrightText = "2017-2021 TRUMPF Laser GmbH"
40+
SPDX-License-Identifier = "MPL-2.0"
41+
42+
[[annotations]]
43+
path = "src/**/**.sln"
44+
precedence = "aggregate"
45+
SPDX-FileCopyrightText = "2017-2021 TRUMPF Laser GmbH"
46+
SPDX-License-Identifier = "MPL-2.0"
47+
48+
[[annotations]]
49+
path = "src/**/**.json"
50+
precedence = "aggregate"
51+
SPDX-FileCopyrightText = "2017-2021 TRUMPF Laser GmbH"
52+
SPDX-License-Identifier = "MPL-2.0"
53+
54+
[[annotations]]
55+
path = "src/**/app.config"
56+
precedence = "aggregate"
57+
SPDX-FileCopyrightText = "2017-2021 TRUMPF Laser GmbH"
58+
SPDX-License-Identifier = "MPL-2.0"

src/Toolbox/SAF.Toolbox.Tests/Filetransfer/FileSenderTests.cs

+26-12
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//
33
// SPDX-License-Identifier: MPL-2.0
44

5+
using Microsoft.Extensions.Options;
56
using NSubstitute;
67
using SAF.Common;
78
using SAF.Toolbox.FileTransfer;
@@ -15,9 +16,9 @@ public class FileSenderTests
1516
[Theory]
1617
[InlineData(1)] // 1 byte
1718
[InlineData(1024)] // 1 kByte
18-
[InlineData(1024 * 3)] // 3 kByte
19+
[InlineData(1024 * 3)] // 3 kByte
1920
[InlineData(1024 * 1024)] // 1 MByte
20-
[InlineData(1024 * 1024 * 3)] // 3 MByte
21+
[InlineData(1024 * 1024 * 3)] // 3 MByte
2122
[InlineData(FileSender.MaxChunkSize - 1)] // excact chunk size - 1
2223
[InlineData(FileSender.MaxChunkSize)] // excact chunk size
2324
[InlineData(FileSender.MaxChunkSize + 1)] // excact chunk size + 1
@@ -30,7 +31,9 @@ public async Task SendInChunksCallsPublishOk(int fileSizeInBytes)
3031
Action<Message>? senderHandler = null;
3132
var messaging = Substitute.For<IMessagingInfrastructure>();
3233
messaging.When(m => m.Subscribe(Arg.Any<string>(), Arg.Any<Action<Message>>()))
33-
.Do(args => senderHandler = args.Arg<Action<Message>>());
34+
.Do(args => senderHandler = args.Arg<Action<Message>>());
35+
var options = Substitute.For<IOptions<FileSenderConfiguration>>();
36+
options.Value.Returns(new FileSenderConfiguration());
3437

3538
var testChannel = $"tests/fileSender/{fileSizeInBytes}";
3639
var buffer = new byte[fileSizeInBytes];
@@ -43,7 +46,7 @@ public async Task SendInChunksCallsPublishOk(int fileSizeInBytes)
4346
});
4447
using (var tempFile = new TemporaryFile($"file{fileSizeInBytes}.tmp", buffer))
4548
{
46-
var fileSender = new FileSender(messaging, null);
49+
var fileSender = new FileSender(messaging, null, options);
4750
var sendResult = await fileSender.SendInChunks(testChannel, tempFile.TempFilePath);
4851
Assert.Equal(FileTransferStatus.Delivered, sendResult);
4952
}
@@ -56,9 +59,9 @@ public async Task SendInChunksCallsPublishOk(int fileSizeInBytes)
5659
[Theory]
5760
[InlineData(1)] // 1 byte
5861
[InlineData(1024)] // 1 kByte
59-
[InlineData(1024 * 3)] // 3 kByte
62+
[InlineData(1024 * 3)] // 3 kByte
6063
[InlineData(1024 * 1024)] // 1 MByte
61-
[InlineData(1024 * 1024 * 3)] // 3 MByte
64+
[InlineData(1024 * 1024 * 3)] // 3 MByte
6265
[InlineData(FileSender.MaxChunkSize - 1)] // excact chunk size - 1
6366
[InlineData(FileSender.MaxChunkSize)] // excact chunk size
6467
[InlineData(FileSender.MaxChunkSize + 1)] // excact chunk size + 1
@@ -71,6 +74,8 @@ public async Task SendInChunksAllowsWriteAccessToFileAfterSendingLastChunkOk(int
7174
var messaging = Substitute.For<IMessagingInfrastructure>();
7275
messaging.When(m => m.Subscribe(Arg.Any<string>(), Arg.Any<Action<Message>>()))
7376
.Do(args => senderHandler = args.Arg<Action<Message>>());
77+
var options = Substitute.For<IOptions<FileSenderConfiguration>>();
78+
options.Value.Returns(new FileSenderConfiguration());
7479

7580
var testChannel = $"tests/fileSender/{fileSizeInBytes}";
7681
var buffer = new byte[fileSizeInBytes];
@@ -93,7 +98,7 @@ public async Task SendInChunksAllowsWriteAccessToFileAfterSendingLastChunkOk(int
9398
senderHandler?.Invoke(new Message { Topic = req.ReplyTo, Payload = "OK" });
9499
});
95100

96-
var fileSender = new FileSender(messaging, null);
101+
var fileSender = new FileSender(messaging, null, options);
97102
var sendResult = await fileSender.SendInChunks(testChannel, tempFile.TempFilePath);
98103
Assert.Equal(FileTransferStatus.Delivered, sendResult);
99104
}
@@ -121,6 +126,8 @@ public async Task SendInChunksUsesSameUniqueTransferIdForEachChunkOk(int fileSiz
121126
var messaging = Substitute.For<IMessagingInfrastructure>();
122127
messaging.When(m => m.Subscribe(Arg.Any<string>(), Arg.Any<Action<Message>>()))
123128
.Do(args => senderHandler = args.Arg<Action<Message>>());
129+
var options = Substitute.For<IOptions<FileSenderConfiguration>>();
130+
options.Value.Returns(new FileSenderConfiguration());
124131

125132
var testChannel = $"tests/fileSender/{fileSizeInBytes}";
126133
var buffer = new byte[fileSizeInBytes];
@@ -139,7 +146,7 @@ public async Task SendInChunksUsesSameUniqueTransferIdForEachChunkOk(int fileSiz
139146
});
140147
using (var tempFile = new TemporaryFile($"file{fileSizeInBytes}.tmp", buffer))
141148
{
142-
var fileSender = new FileSender(messaging, null);
149+
var fileSender = new FileSender(messaging, null, options);
143150
var sendResult = await fileSender.SendInChunks(testChannel, tempFile.TempFilePath);
144151
Assert.Equal(FileTransferStatus.Delivered, sendResult);
145152
}
@@ -149,23 +156,30 @@ public async Task SendInChunksUsesSameUniqueTransferIdForEachChunkOk(int fileSiz
149156
messaging.Received(Convert.ToInt32(expectedCalls)).Publish(Arg.Is<Message>(msg => msg.Topic == testChannel));
150157
}
151158

152-
[Fact]
153-
public async Task SendInChunksWithMissingAnswerReturnsTimedOutOk()
159+
[Theory]
160+
[InlineData(1)]
161+
[InlineData(4)]
162+
public async Task SendInChunksWithMissingAnswerReturnsTimedOutOk(int expectedCalls)
154163
{
155164
const int fileSizeInBytes = 1024;
156165

157166
var messaging = Substitute.For<IMessagingInfrastructure>();
167+
var options = Substitute.For<IOptions<FileSenderConfiguration>>();
168+
options.Value.Returns(new FileSenderConfiguration
169+
{
170+
RetryAttemptsForFailedChunks = expectedCalls - 1
171+
});
158172

159173
var testChannel = $"tests/fileSender/{fileSizeInBytes}";
160174
var buffer = new byte[fileSizeInBytes];
161175
using (var tempFile = new TemporaryFile($"file{fileSizeInBytes}.tst", buffer))
162176
{
163-
var fileSender = new FileSender(messaging, null);
177+
var fileSender = new FileSender(messaging, null, options);
178+
fileSender.Timeout = 2000;
164179
var sendResult = await fileSender.SendInChunks(testChannel, tempFile.TempFilePath);
165180
Assert.Equal(FileTransferStatus.TimedOut, sendResult);
166181
}
167182

168-
var expectedCalls = 1;
169183
messaging.Received(expectedCalls).Publish(Arg.Is<Message>(msg => msg.Topic == testChannel));
170184
}
171185
}

src/Toolbox/SAF.Toolbox.Tests/SAF.Toolbox.Tests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ SPDX-License-Identifier: MPL-2.0
1212
</PropertyGroup>
1313

1414
<ItemGroup>
15+
<PackageReference Include="Microsoft.Extensions.Configuration" />
1516
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
1617
<PackageReference Include="Microsoft.NET.Test.Sdk" />
1718
<PackageReference Include="NSubstitute" />

src/Toolbox/SAF.Toolbox.Tests/ServiceCollectionExtensionsTests.cs

+54-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
// SPDX-FileCopyrightText: 2017-2021 TRUMPF Laser GmbH
22
//
33
// SPDX-License-Identifier: MPL-2.0
4+
5+
using Microsoft.Extensions.Configuration;
46
using Microsoft.Extensions.DependencyInjection;
7+
using Microsoft.Extensions.Logging;
8+
using Microsoft.Extensions.Options;
59
using NSubstitute;
610
using SAF.Common;
11+
using SAF.Toolbox.FileTransfer;
712
using SAF.Toolbox.Heartbeat;
813
using SAF.Toolbox.RequestClient;
914
using Xunit;
@@ -47,7 +52,7 @@ public void AddHeartbeatPoolAddsServiceOnlyOnceOk()
4752
[Fact]
4853
public void AddRequestClientAddsServiceAndRequiredServicesOk()
4954
{
50-
_services.AddSingleton(sp => Substitute.For<IMessagingInfrastructure>());
55+
_services.AddSingleton(_ => Substitute.For<IMessagingInfrastructure>());
5156
_services.AddRequestClient();
5257

5358
using var provider = _services.BuildServiceProvider();
@@ -58,7 +63,7 @@ public void AddRequestClientAddsServiceAndRequiredServicesOk()
5863
[Fact]
5964
public void AddRequestClientAddsServiceAfterAddHeartbeatPoolOk()
6065
{
61-
_services.AddSingleton(sp => Substitute.For<IMessagingInfrastructure>());
66+
_services.AddSingleton(_ => Substitute.For<IMessagingInfrastructure>());
6267
_services.AddHeartbeatPool();
6368
_services.AddRequestClient();
6469

@@ -70,7 +75,7 @@ public void AddRequestClientAddsServiceAfterAddHeartbeatPoolOk()
7075
[Fact]
7176
public void AddRequestClientAddsServiceOnlyOnceOk()
7277
{
73-
_services.AddSingleton(sp => Substitute.For<IMessagingInfrastructure>());
78+
_services.AddSingleton(_ => Substitute.For<IMessagingInfrastructure>());
7479

7580
_services.AddRequestClient();
7681
_services.AddRequestClient();
@@ -82,4 +87,50 @@ public void AddRequestClientAddsServiceOnlyOnceOk()
8287
Assert.Single(provider.GetServices<IRequestClient>());
8388
Assert.NotNull(provider.GetService<IRequestClient>());
8489
}
90+
91+
[Fact]
92+
public void AddFileSenderWithoutConfigAddsServiceWithDefaultConfigOk()
93+
{
94+
// Arrange
95+
_services.AddSingleton(_ => Substitute.For<IMessagingInfrastructure>());
96+
_services.AddSingleton(_ => Substitute.For<ILogger<FileSender>>());
97+
98+
// Act
99+
_services.AddFileSender();
100+
101+
using var provider = _services.BuildServiceProvider();
102+
var fileSender = provider.GetService<IFileSender>();
103+
var options = provider.GetService<IOptions<FileSenderConfiguration>>();
104+
105+
// Assert
106+
Assert.NotNull(fileSender);
107+
Assert.NotNull(options);
108+
Assert.NotNull(options.Value);
109+
Assert.Equal(0, options.Value.RetryAttemptsForFailedChunks);
110+
}
111+
112+
[Fact]
113+
public void AddFileSenderWithConfigAddsServiceWithSpecificConfigOk()
114+
{
115+
// Arrange
116+
_services.AddSingleton(_ => Substitute.For<IMessagingInfrastructure>());
117+
_services.AddSingleton(_ => Substitute.For<ILogger<FileSender>>());
118+
var config = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string>
119+
{
120+
{ "FileSender:RetryAttemptsForFailedChunks", "5" }
121+
}!).Build();
122+
123+
// Act
124+
_services.AddFileSender(config);
125+
126+
using var provider = _services.BuildServiceProvider();
127+
var fileSender = provider.GetService<IFileSender>();
128+
var options = provider.GetService<IOptions<FileSenderConfiguration>>();
129+
130+
// Assert
131+
Assert.NotNull(fileSender);
132+
Assert.NotNull(options);
133+
Assert.NotNull(options.Value);
134+
Assert.Equal(5, options.Value.RetryAttemptsForFailedChunks);
135+
}
85136
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// SPDX-FileCopyrightText: 2017-2024 TRUMPF Laser GmbH
2+
//
3+
// SPDX-License-Identifier: MPL-2.0
4+
5+
namespace SAF.Toolbox;
6+
7+
public class FileSenderConfiguration
8+
{
9+
public int RetryAttemptsForFailedChunks { get; set; } = 0;
10+
}

0 commit comments

Comments
 (0)