diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml
index 1a850444..315ff80a 100644
--- a/.github/workflows/github-actions.yml
+++ b/.github/workflows/github-actions.yml
@@ -117,7 +117,7 @@ jobs:
- name: '📡 Publish Test Results' # https://github.com/marketplace/actions/publish-test-results
uses: 'EnricoMi/publish-unit-test-result-action/macos@v2'
- if: always()
+ if: always() # this means: "run even if prior steps fail" if tests fail, the build will fail, but at least we will know exactly what failed
with:
files: |
TestResults/**/TEST-*.xml
diff --git a/Laerdal.McuMgr.Bindings.Android/Laerdal.McuMgr.Bindings.Android.csproj b/Laerdal.McuMgr.Bindings.Android/Laerdal.McuMgr.Bindings.Android.csproj
index e8923eb5..1f20100e 100644
--- a/Laerdal.McuMgr.Bindings.Android/Laerdal.McuMgr.Bindings.Android.csproj
+++ b/Laerdal.McuMgr.Bindings.Android/Laerdal.McuMgr.Bindings.Android.csproj
@@ -62,10 +62,10 @@
true
- 1.0.1087.0
- 1.0.1087.0
- 1.0.1087.0
- 1.0.1087.0
+ 1.0.1092.0
+ 1.0.1092.0
+ 1.0.1092.0
+ 1.0.1092.0
$(PackageId)
$(Authors)
diff --git a/Laerdal.McuMgr.Bindings.MacCatalyst/Laerdal.McuMgr.Bindings.MacCatalyst.csproj b/Laerdal.McuMgr.Bindings.MacCatalyst/Laerdal.McuMgr.Bindings.MacCatalyst.csproj
index 6b802181..8d523370 100644
--- a/Laerdal.McuMgr.Bindings.MacCatalyst/Laerdal.McuMgr.Bindings.MacCatalyst.csproj
+++ b/Laerdal.McuMgr.Bindings.MacCatalyst/Laerdal.McuMgr.Bindings.MacCatalyst.csproj
@@ -77,10 +77,10 @@
$(AllowedReferenceRelatedFileExtensions);.pdb
- 1.0.1177.0
- 1.0.1177.0
- 1.0.1177.0
- 1.0.1177.0
+ 1.0.1092.0
+ 1.0.1092.0
+ 1.0.1092.0
+ 1.0.1092.0
$(PackageId)
McuMgr Bindings for MacCatalyst - MAUI ready
diff --git a/Laerdal.McuMgr.Bindings.NetStandard/Laerdal.McuMgr.Bindings.NetStandard.csproj b/Laerdal.McuMgr.Bindings.NetStandard/Laerdal.McuMgr.Bindings.NetStandard.csproj
index f3e3ca9b..db96c965 100644
--- a/Laerdal.McuMgr.Bindings.NetStandard/Laerdal.McuMgr.Bindings.NetStandard.csproj
+++ b/Laerdal.McuMgr.Bindings.NetStandard/Laerdal.McuMgr.Bindings.NetStandard.csproj
@@ -37,10 +37,10 @@
$(AllowedReferenceRelatedFileExtensions);.pdb
- 1.0.1177.0
- 1.0.1177.0
- 1.0.1177.0
- 1.0.1177.0
+ 1.0.1092.0
+ 1.0.1092.0
+ 1.0.1092.0
+ 1.0.1092.0
$(PackageId)
McuMgr C# Implementation (WIP)
diff --git a/Laerdal.McuMgr.Bindings.iOS/Laerdal.McuMgr.Bindings.iOS.csproj b/Laerdal.McuMgr.Bindings.iOS/Laerdal.McuMgr.Bindings.iOS.csproj
index dd0dcdf3..0c749715 100644
--- a/Laerdal.McuMgr.Bindings.iOS/Laerdal.McuMgr.Bindings.iOS.csproj
+++ b/Laerdal.McuMgr.Bindings.iOS/Laerdal.McuMgr.Bindings.iOS.csproj
@@ -74,10 +74,10 @@
$(AllowedReferenceRelatedFileExtensions);.pdb
- 1.0.1177.0
- 1.0.1177.0
- 1.0.1177.0
- 1.0.1177.0
+ 1.0.1092.0
+ 1.0.1092.0
+ 1.0.1092.0
+ 1.0.1092.0
$(PackageId)
McuMgr Bindings for iOS - MAUI ready
diff --git a/Laerdal.McuMgr.Tests/DeviceResetter/DeviceResetterTestbed.ResetAsync.ShouldThrowDeviceResetterErroredOutException_GivenBluetoothErrorDuringReset.cs b/Laerdal.McuMgr.Tests/DeviceResetter/DeviceResetterTestbed.ResetAsync.ShouldThrowDeviceResetterErroredOutException_GivenBluetoothErrorDuringReset.cs
index d3bf3efd..5d5dc56b 100644
--- a/Laerdal.McuMgr.Tests/DeviceResetter/DeviceResetterTestbed.ResetAsync.ShouldThrowDeviceResetterErroredOutException_GivenBluetoothErrorDuringReset.cs
+++ b/Laerdal.McuMgr.Tests/DeviceResetter/DeviceResetterTestbed.ResetAsync.ShouldThrowDeviceResetterErroredOutException_GivenBluetoothErrorDuringReset.cs
@@ -1,4 +1,5 @@
using FluentAssertions;
+using FluentAssertions.Extensions;
using Laerdal.McuMgr.Common.Enums;
using Laerdal.McuMgr.Common.Helpers;
using Laerdal.McuMgr.DeviceResetter.Contracts.Enums;
@@ -23,8 +24,7 @@ public async Task ResetAsync_ShouldThrowDeviceResetterErroredOutException_GivenB
// Assert
await work
- .Should().ThrowExactlyAsync()
- .WithTimeoutInMs(500)
+ .Should().ThrowWithinAsync(500.Milliseconds())
.WithMessage("*bluetooth error blah blah*");
mockedNativeDeviceResetterProxy.DisconnectCalled.Should().BeFalse(); //00
diff --git a/Laerdal.McuMgr.Tests/DeviceResetter/DeviceResetterTestbed.ResetAsync.ShouldThrowDeviceResetterInternalErrorException_GivenErroneousDueToMissingNativeSymbolsNativeDeviceResetterProxy.cs b/Laerdal.McuMgr.Tests/DeviceResetter/DeviceResetterTestbed.ResetAsync.ShouldThrowDeviceResetterInternalErrorException_GivenErroneousDueToMissingNativeSymbolsNativeDeviceResetterProxy.cs
index 41bb8f18..6083ecb7 100644
--- a/Laerdal.McuMgr.Tests/DeviceResetter/DeviceResetterTestbed.ResetAsync.ShouldThrowDeviceResetterInternalErrorException_GivenErroneousDueToMissingNativeSymbolsNativeDeviceResetterProxy.cs
+++ b/Laerdal.McuMgr.Tests/DeviceResetter/DeviceResetterTestbed.ResetAsync.ShouldThrowDeviceResetterInternalErrorException_GivenErroneousDueToMissingNativeSymbolsNativeDeviceResetterProxy.cs
@@ -1,4 +1,5 @@
using FluentAssertions;
+using FluentAssertions.Extensions;
using Laerdal.McuMgr.Common.Helpers;
using Laerdal.McuMgr.DeviceResetter.Contracts.Enums;
using Laerdal.McuMgr.DeviceResetter.Contracts.Events;
@@ -21,7 +22,10 @@ public async Task ResetAsync_ShouldThrowDeviceResetterInternalErrorException_Giv
var work = new Func(() => deviceResetter.ResetAsync());
// Assert
- (await work.Should().ThrowExactlyAsync().WithTimeoutInMs(100)).WithInnerExceptionExactly("native symbols not loaded blah blah");
+ (await work
+ .Should()
+ .ThrowWithinAsync(500.Milliseconds())
+ ).WithInnerExceptionExactly("native symbols not loaded blah blah");
mockedNativeDeviceResetterProxy.DisconnectCalled.Should().BeFalse(); //00
mockedNativeDeviceResetterProxy.BeginResetCalled.Should().BeTrue();
diff --git a/Laerdal.McuMgr.Tests/DeviceResetter/DeviceResetterTestbed.ResetAsync.ShouldThrowTimeoutException_GivenTooSmallTimeout.cs b/Laerdal.McuMgr.Tests/DeviceResetter/DeviceResetterTestbed.ResetAsync.ShouldThrowTimeoutException_GivenTooSmallTimeout.cs
index bb1e4bc5..c166a162 100644
--- a/Laerdal.McuMgr.Tests/DeviceResetter/DeviceResetterTestbed.ResetAsync.ShouldThrowTimeoutException_GivenTooSmallTimeout.cs
+++ b/Laerdal.McuMgr.Tests/DeviceResetter/DeviceResetterTestbed.ResetAsync.ShouldThrowTimeoutException_GivenTooSmallTimeout.cs
@@ -22,7 +22,9 @@ public async Task ResetAsync_ShouldThrowTimeoutException_GivenTooSmallTimeout()
var work = new Func(() => deviceResetter.ResetAsync(timeoutInMs: 100));
// Assert
- await work.Should().ThrowExactlyAsync().WithTimeoutInMs((int)5.Seconds().TotalMilliseconds);
+ await work
+ .Should()
+ .ThrowWithinAsync(5.Seconds());
mockedNativeDeviceResetterProxy.DisconnectCalled.Should().BeFalse(); //00
mockedNativeDeviceResetterProxy.BeginResetCalled.Should().BeTrue();
diff --git a/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.MultipleFilesDownloadAsync.ShouldThrowArgumentException_GivenPathCollectionWithErroneousFilesToDownload.cs b/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.MultipleFilesDownloadAsync.ShouldThrowArgumentException_GivenPathCollectionWithErroneousFilesToDownload.cs
index a764b37f..c5ed6170 100644
--- a/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.MultipleFilesDownloadAsync.ShouldThrowArgumentException_GivenPathCollectionWithErroneousFilesToDownload.cs
+++ b/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.MultipleFilesDownloadAsync.ShouldThrowArgumentException_GivenPathCollectionWithErroneousFilesToDownload.cs
@@ -1,4 +1,5 @@
using FluentAssertions;
+using FluentAssertions.Extensions;
using Laerdal.McuMgr.Common.Helpers;
using Laerdal.McuMgr.FileDownloader.Contracts.Enums;
using Laerdal.McuMgr.FileDownloader.Contracts.Native;
@@ -37,7 +38,7 @@ public async Task MultipleFilesDownloadAsync_ShouldThrowArgumentException_GivenP
));
// Assert
- await work.Should().ThrowExactlyAsync().WithTimeoutInMs(500);
+ await work.Should().ThrowWithinAsync(500.Milliseconds());
eventsMonitor.OccurredEvents.Should().HaveCount(0);
diff --git a/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.MultipleFilesDownloadAsync.ShouldThrowNullArgumentException_GivenNullForFilesToDownload.cs b/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.MultipleFilesDownloadAsync.ShouldThrowNullArgumentException_GivenNullForFilesToDownload.cs
index bc9f6686..4538c6d0 100644
--- a/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.MultipleFilesDownloadAsync.ShouldThrowNullArgumentException_GivenNullForFilesToDownload.cs
+++ b/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.MultipleFilesDownloadAsync.ShouldThrowNullArgumentException_GivenNullForFilesToDownload.cs
@@ -1,4 +1,5 @@
using FluentAssertions;
+using FluentAssertions.Extensions;
using Laerdal.McuMgr.Common.Helpers;
using Laerdal.McuMgr.FileDownloader.Contracts.Enums;
using Laerdal.McuMgr.FileDownloader.Contracts.Native;
@@ -28,7 +29,7 @@ public async Task MultipleFilesDownloadAsync_ShouldThrowNullArgumentException_Gi
));
// Assert
- await work.Should().ThrowExactlyAsync().WithTimeoutInMs(500);
+ await work.Should().ThrowWithinAsync(500.Milliseconds());
eventsMonitor.OccurredEvents.Should().HaveCount(0);
diff --git a/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowAllDownloadAttemptsFailedException_GivenFatalErrorMidflight.cs b/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowAllDownloadAttemptsFailedException_GivenFatalErrorMidflight.cs
index 1139edb6..8354715f 100644
--- a/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowAllDownloadAttemptsFailedException_GivenFatalErrorMidflight.cs
+++ b/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowAllDownloadAttemptsFailedException_GivenFatalErrorMidflight.cs
@@ -40,9 +40,8 @@ public async Task SingleFileDownloadAsync_ShouldThrowAllDownloadAttemptsFailedEx
// Assert
await work.Should()
- .ThrowExactlyAsync()
- .WithMessage("*failed to download*")
- .WithTimeoutInMs((int)(maxTriesCount * 3).Seconds().TotalMilliseconds);
+ .ThrowWithinAsync((maxTriesCount * 3).Seconds())
+ .WithMessage("*failed to download*");
mockedNativeFileDownloaderProxy.CancelCalled.Should().BeFalse();
mockedNativeFileDownloaderProxy.DisconnectCalled.Should().BeFalse(); //00
diff --git a/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowAllDownloadAttemptsFailedException_GivenRogueNativeErrorMessage.cs b/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowAllDownloadAttemptsFailedException_GivenRogueNativeErrorMessage.cs
index 2f33cb71..a95ca49a 100644
--- a/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowAllDownloadAttemptsFailedException_GivenRogueNativeErrorMessage.cs
+++ b/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowAllDownloadAttemptsFailedException_GivenRogueNativeErrorMessage.cs
@@ -45,8 +45,7 @@ public async Task SingleFileDownloadAsync_ShouldThrowAllDownloadAttemptsFailedEx
// Assert
await work.Should()
- .ThrowExactlyAsync()
- .WithTimeoutInMs((int)3.Seconds().TotalMilliseconds);
+ .ThrowWithinAsync(3.Seconds());
mockedNativeFileDownloaderProxy.CancelCalled.Should().BeFalse();
mockedNativeFileDownloaderProxy.DisconnectCalled.Should().BeFalse(); //00
diff --git a/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowArgumentException_GivenEmptyRemoteFilePath.cs b/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowArgumentException_GivenEmptyRemoteFilePath.cs
index 5a2ef7a1..bff419ed 100644
--- a/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowArgumentException_GivenEmptyRemoteFilePath.cs
+++ b/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowArgumentException_GivenEmptyRemoteFilePath.cs
@@ -1,4 +1,5 @@
using FluentAssertions;
+using FluentAssertions.Extensions;
using Laerdal.McuMgr.Common.Helpers;
using Laerdal.McuMgr.FileDownloader.Contracts.Enums;
using Laerdal.McuMgr.FileDownloader.Contracts.Native;
@@ -29,7 +30,7 @@ public async Task SingleFileDownloadAsync_ShouldThrowArgumentException_GivenEmpt
));
// Assert
- await work.Should().ThrowExactlyAsync().WithTimeoutInMs(500);
+ await work.Should().ThrowWithinAsync(500.Milliseconds());
mockedNativeFileDownloaderProxy.CancelCalled.Should().BeFalse();
mockedNativeFileDownloaderProxy.DisconnectCalled.Should().BeFalse(); //00
diff --git a/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowDownloadCancelledException_GivenCancellationRequestMidflight.cs b/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowDownloadCancelledException_GivenCancellationRequestMidflight.cs
index 216db887..2943a358 100644
--- a/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowDownloadCancelledException_GivenCancellationRequestMidflight.cs
+++ b/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowDownloadCancelledException_GivenCancellationRequestMidflight.cs
@@ -41,7 +41,7 @@ public async Task SingleFileUploadAsync_ShouldThrowUploadCancelledException_Give
));
// Assert
- await work.Should().ThrowExactlyAsync().WithTimeoutInMs((int)5.Seconds().TotalMilliseconds);
+ await work.Should().ThrowWithinAsync(5.Seconds());
mockedNativeFileDownloaderProxy.CancelCalled.Should().BeTrue();
mockedNativeFileDownloaderProxy.DisconnectCalled.Should().BeFalse(); //00
diff --git a/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowDownloadTimeoutException_GivenTooSmallTimeout.cs b/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowDownloadTimeoutException_GivenTooSmallTimeout.cs
index 1752b3c1..f7a2e0b6 100644
--- a/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowDownloadTimeoutException_GivenTooSmallTimeout.cs
+++ b/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowDownloadTimeoutException_GivenTooSmallTimeout.cs
@@ -32,7 +32,7 @@ public async Task SingleFileDownloadAsync_ShouldThrowDownloadTimeoutException_Gi
));
// Assert
- await work.Should().ThrowExactlyAsync().WithTimeoutInMs((int)5.Seconds().TotalMilliseconds);
+ await work.Should().ThrowWithinAsync(5.Seconds());
mockedNativeFileDownloaderProxy.CancelCalled.Should().BeFalse();
mockedNativeFileDownloaderProxy.DisconnectCalled.Should().BeFalse(); //00
diff --git a/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowRemoteFileNotFoundException_GivenNonExistentFilepath.cs b/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowRemoteFileNotFoundException_GivenNonExistentFilepath.cs
index 1eb9e6b4..7a15801d 100644
--- a/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowRemoteFileNotFoundException_GivenNonExistentFilepath.cs
+++ b/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowRemoteFileNotFoundException_GivenNonExistentFilepath.cs
@@ -46,8 +46,7 @@ public async Task SingleFileDownloadAsync_ShouldThrowRemoteFileNotFoundException
// Assert
await work.Should()
- .ThrowExactlyAsync()
- .WithTimeoutInMs((int)3.Seconds().TotalMilliseconds);
+ .ThrowWithinAsync(3.Seconds());
mockedNativeFileDownloaderProxy.CancelCalled.Should().BeFalse();
mockedNativeFileDownloaderProxy.DisconnectCalled.Should().BeFalse(); //00
diff --git a/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.MultipleFilesUploadAsync.ShouldThrowArgumentException_GivenPathCollectionWithErroneousFilesToDownload.cs b/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.MultipleFilesUploadAsync.ShouldThrowArgumentException_GivenPathCollectionWithErroneousFilesToDownload.cs
index 4a12f1c3..b74b7a8c 100644
--- a/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.MultipleFilesUploadAsync.ShouldThrowArgumentException_GivenPathCollectionWithErroneousFilesToDownload.cs
+++ b/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.MultipleFilesUploadAsync.ShouldThrowArgumentException_GivenPathCollectionWithErroneousFilesToDownload.cs
@@ -1,4 +1,5 @@
using FluentAssertions;
+using FluentAssertions.Extensions;
using Laerdal.McuMgr.Common.Helpers;
using Laerdal.McuMgr.FileUploader.Contracts.Enums;
using Laerdal.McuMgr.FileUploader.Contracts.Native;
@@ -36,7 +37,7 @@ public async Task MultipleFilesUploadAsync_ShouldThrowArgumentException_GivenPat
));
// Assert
- await work.Should().ThrowAsync().WithTimeoutInMs(500); //dont use throwexactlyasync<> here
+ await work.Should().ThrowWithinAsync(500.Milliseconds()); //dont use throwexactlyasync<> here
eventsMonitor.OccurredEvents.Should().HaveCount(0);
diff --git a/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.MultipleFilesUploadAsync.ShouldThrowNullArgumentException_GivenNullForFilesToDownload.cs b/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.MultipleFilesUploadAsync.ShouldThrowNullArgumentException_GivenNullForFilesToDownload.cs
index 1dcfda94..c1dbcc5a 100644
--- a/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.MultipleFilesUploadAsync.ShouldThrowNullArgumentException_GivenNullForFilesToDownload.cs
+++ b/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.MultipleFilesUploadAsync.ShouldThrowNullArgumentException_GivenNullForFilesToDownload.cs
@@ -1,4 +1,5 @@
using FluentAssertions;
+using FluentAssertions.Extensions;
using Laerdal.McuMgr.Common.Helpers;
using Laerdal.McuMgr.FileUploader.Contracts.Enums;
using Laerdal.McuMgr.FileUploader.Contracts.Native;
@@ -28,7 +29,7 @@ public async Task MultipleFilesUploadAsync_ShouldThrowNullArgumentException_Give
));
// Assert
- await work.Should().ThrowExactlyAsync().WithTimeoutInMs(500);
+ await work.Should().ThrowWithinAsync(500.Milliseconds());
eventsMonitor.OccurredEvents.Should().HaveCount(0);
diff --git a/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowAllUploadAttemptsFailedException_GivenFatalErrorMidflight.cs b/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowAllUploadAttemptsFailedException_GivenFatalErrorMidflight.cs
index eadf067f..e74725d8 100644
--- a/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowAllUploadAttemptsFailedException_GivenFatalErrorMidflight.cs
+++ b/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowAllUploadAttemptsFailedException_GivenFatalErrorMidflight.cs
@@ -40,9 +40,8 @@ public async Task SingleFileUploadAsync_ShouldThrowAllUploadAttemptsFailedExcept
// Assert
await work.Should()
- .ThrowExactlyAsync()
- .WithMessage("*failed to upload*")
- .WithTimeoutInMs((int)((maxTriesCount + 1) * 3).Seconds().TotalMilliseconds);
+ .ThrowWithinAsync(((maxTriesCount + 1) * 3).Seconds())
+ .WithMessage("*failed to upload*");
mockedNativeFileUploaderProxy.CancelCalled.Should().BeFalse();
mockedNativeFileUploaderProxy.DisconnectCalled.Should().BeFalse(); //00
diff --git a/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowAllUploadAttemptsFailedException_GivenRogueNativeErrorMessage.cs b/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowAllUploadAttemptsFailedException_GivenRogueNativeErrorMessage.cs
index 1938baf5..35edf175 100644
--- a/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowAllUploadAttemptsFailedException_GivenRogueNativeErrorMessage.cs
+++ b/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowAllUploadAttemptsFailedException_GivenRogueNativeErrorMessage.cs
@@ -43,9 +43,7 @@ public async Task SingleFileUploadAsync_ShouldThrowAllUploadAttemptsFailedExcept
));
// Assert
- await work.Should()
- .ThrowExactlyAsync()
- .WithTimeoutInMs((int)3.Seconds().TotalMilliseconds);
+ await work.Should().ThrowWithinAsync(3.Seconds());
mockedNativeFileUploaderProxy.CancelCalled.Should().BeFalse();
mockedNativeFileUploaderProxy.DisconnectCalled.Should().BeFalse(); //00
diff --git a/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowArgumentException_GivenEmptyRemoteFilePath.cs b/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowArgumentException_GivenEmptyRemoteFilePath.cs
index 2111487b..b23051bc 100644
--- a/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowArgumentException_GivenEmptyRemoteFilePath.cs
+++ b/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowArgumentException_GivenEmptyRemoteFilePath.cs
@@ -1,4 +1,5 @@
using FluentAssertions;
+using FluentAssertions.Extensions;
using Laerdal.McuMgr.Common.Helpers;
using Laerdal.McuMgr.FileUploader.Contracts.Enums;
using Laerdal.McuMgr.FileUploader.Contracts.Native;
@@ -30,7 +31,7 @@ public async Task SingleFileUploadAsync_ShouldThrowArgumentException_GivenEmptyR
));
// Assert
- await work.Should().ThrowExactlyAsync().WithTimeoutInMs(500);
+ await work.Should().ThrowWithinAsync(500.Milliseconds());
mockedNativeFileUploaderProxy.CancelCalled.Should().BeFalse();
mockedNativeFileUploaderProxy.DisconnectCalled.Should().BeFalse(); //00
diff --git a/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowRemoteFolderNotFoundException_GivenNonExistentFilepath.cs b/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowRemoteFolderNotFoundException_GivenNonExistentFilepath.cs
index 40dd16f0..a9dc25a7 100644
--- a/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowRemoteFolderNotFoundException_GivenNonExistentFilepath.cs
+++ b/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowRemoteFolderNotFoundException_GivenNonExistentFilepath.cs
@@ -52,8 +52,7 @@ int maxTriesCount
// Assert
await work.Should()
- .ThrowExactlyAsync()
- .WithTimeoutInMs((int)3.Seconds().TotalMilliseconds);
+ .ThrowWithinAsync(3.Seconds());
mockedNativeFileUploaderProxy.CancelCalled.Should().BeFalse();
mockedNativeFileUploaderProxy.DisconnectCalled.Should().BeFalse(); //00
diff --git a/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowUploadCancelledException_GivenCancellationRequestMidflight.cs b/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowUploadCancelledException_GivenCancellationRequestMidflight.cs
index a05b76f0..c435d320 100644
--- a/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowUploadCancelledException_GivenCancellationRequestMidflight.cs
+++ b/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowUploadCancelledException_GivenCancellationRequestMidflight.cs
@@ -43,7 +43,7 @@ public async Task SingleFileUploadAsync_ShouldThrowUploadCancelledException_Give
));
// Assert
- await work.Should().ThrowExactlyAsync().WithTimeoutInMs((int)5.Seconds().TotalMilliseconds);
+ await work.Should().ThrowWithinAsync(5.Seconds());
mockedNativeFileUploaderProxy.CancelCalled.Should().BeTrue();
mockedNativeFileUploaderProxy.CancellationReason.Should().Be(cancellationReason);
diff --git a/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowUploadTimeoutException_GivenTooSmallTimeout.cs b/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowUploadTimeoutException_GivenTooSmallTimeout.cs
index 4e0e1ca4..cbf50b8e 100644
--- a/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowUploadTimeoutException_GivenTooSmallTimeout.cs
+++ b/Laerdal.McuMgr.Tests/FileUploader/FileUploaderTestbed.SingleFileUploadAsync.ShouldThrowUploadTimeoutException_GivenTooSmallTimeout.cs
@@ -33,7 +33,7 @@ public async Task SingleFileUploadAsync_ShouldThrowUploadTimeoutException_GivenT
));
// Assert
- await work.Should().ThrowExactlyAsync().WithTimeoutInMs((int)5.Seconds().TotalMilliseconds);
+ await work.Should().ThrowWithinAsync(5.Seconds());
mockedNativeFileUploaderProxy.CancelCalled.Should().BeFalse();
mockedNativeFileUploaderProxy.DisconnectCalled.Should().BeFalse(); //00
diff --git a/Laerdal.McuMgr.Tests/FirmwareEraser/FirmwareEraserTestbed.EraseAsync.ShouldThrowTimeoutException_GivenTooSmallTimeout.cs b/Laerdal.McuMgr.Tests/FirmwareEraser/FirmwareEraserTestbed.EraseAsync.ShouldThrowTimeoutException_GivenTooSmallTimeout.cs
index 67277ff5..c2aaa792 100644
--- a/Laerdal.McuMgr.Tests/FirmwareEraser/FirmwareEraserTestbed.EraseAsync.ShouldThrowTimeoutException_GivenTooSmallTimeout.cs
+++ b/Laerdal.McuMgr.Tests/FirmwareEraser/FirmwareEraserTestbed.EraseAsync.ShouldThrowTimeoutException_GivenTooSmallTimeout.cs
@@ -22,7 +22,7 @@ public async Task EraseAsync_ShouldThrowTimeoutException_GivenTooSmallTimeout()
var work = new Func(() => firmwareEraser.EraseAsync(imageIndex: 2, timeoutInMs: 100));
// Assert
- await work.Should().ThrowExactlyAsync().WithTimeoutInMs((int)5.Seconds().TotalMilliseconds);
+ await work.Should().ThrowWithinAsync(5.Seconds());
mockedNativeFirmwareEraserProxy.DisconnectCalled.Should().BeFalse(); //00
mockedNativeFirmwareEraserProxy.BeginErasureCalled.Should().BeTrue();
diff --git a/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowAllFirmwareInstallationAttemptsFailedException_GivenFirmwareUploadFatalErrorMidflight.cs b/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowAllFirmwareInstallationAttemptsFailedException_GivenFirmwareUploadFatalErrorMidflight.cs
index 15292531..3f6a030d 100644
--- a/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowAllFirmwareInstallationAttemptsFailedException_GivenFirmwareUploadFatalErrorMidflight.cs
+++ b/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowAllFirmwareInstallationAttemptsFailedException_GivenFirmwareUploadFatalErrorMidflight.cs
@@ -1,4 +1,5 @@
using FluentAssertions;
+using FluentAssertions.Extensions;
using Laerdal.McuMgr.Common.Enums;
using Laerdal.McuMgr.Common.Helpers;
using Laerdal.McuMgr.FirmwareInstaller.Contracts.Enums;
@@ -32,9 +33,7 @@ public async Task InstallAsync_ShouldThrowAllFirmwareInstallationAttemptsFailedE
// Assert
(
- await work.Should()
- .ThrowExactlyAsync()
- .WithTimeoutInMs(3_000)
+ await work.Should().ThrowWithinAsync(3_000.Milliseconds())
).WithInnerException();
mockedNativeFirmwareInstallerProxy.CancelCalled.Should().BeFalse();
diff --git a/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowAllFirmwareInstallationAttemptsFailedException_GivenGenericFatalErrorMidflight.cs b/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowAllFirmwareInstallationAttemptsFailedException_GivenGenericFatalErrorMidflight.cs
index 1ddceb23..15c815ea 100644
--- a/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowAllFirmwareInstallationAttemptsFailedException_GivenGenericFatalErrorMidflight.cs
+++ b/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowAllFirmwareInstallationAttemptsFailedException_GivenGenericFatalErrorMidflight.cs
@@ -1,4 +1,5 @@
using FluentAssertions;
+using FluentAssertions.Extensions;
using Laerdal.McuMgr.Common.Enums;
using Laerdal.McuMgr.Common.Helpers;
using Laerdal.McuMgr.FirmwareInstaller.Contracts.Enums;
@@ -30,9 +31,9 @@ public async Task InstallAsync_ShouldThrowAllFirmwareInstallationAttemptsFailedE
// Assert
await work.Should()
- .ThrowExactlyAsync() // todo AllFirmwareInstallationAttemptsFailedException
- .WithMessage("*fatal error occurred*")
- .WithTimeoutInMs(3_000);
+ .ThrowWithinAsync(3_000.Milliseconds())
+ .WithInnerException(typeof(FirmwareInstallationUploadingStageErroredOutException))
+ .WithMessage("*fatal error occurred 123*");
mockedNativeFirmwareInstallerProxy.CancelCalled.Should().BeFalse();
mockedNativeFirmwareInstallerProxy.DisconnectCalled.Should().BeFalse(); //00
@@ -43,7 +44,7 @@ await work.Should()
eventsMonitor
.Should().Raise(nameof(firmwareInstaller.FatalErrorOccurred))
.WithSender(firmwareInstaller)
- .WithArgs(args => args.ErrorMessage == "fatal error occurred");
+ .WithArgs(args => args.ErrorMessage == "fatal error occurred 123");
eventsMonitor
.Should().Raise(nameof(firmwareInstaller.StateChanged))
@@ -103,7 +104,7 @@ public override EFirmwareInstallationVerdict BeginInstallation(
await Task.Delay(100);
StateChangedAdvertisement(EFirmwareInstallationState.Uploading, EFirmwareInstallationState.Error); // order
- FatalErrorOccurredAdvertisement(EFirmwareInstallationState.Confirming, EFirmwareInstallerFatalErrorType.Generic, "fatal error occurred", EGlobalErrorCode.Generic); // order
+ FatalErrorOccurredAdvertisement(EFirmwareInstallationState.Uploading, EFirmwareInstallerFatalErrorType.Generic, "fatal error occurred 123", EGlobalErrorCode.Generic); // order
});
return verdict;
diff --git a/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowFirmwareInstallationImageSwapTimeoutException_GivenFatalErrorMidflight.cs b/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowFirmwareInstallationImageSwapTimeoutException_GivenFatalErrorMidflight.cs
index c74ed713..62cb4c6f 100644
--- a/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowFirmwareInstallationImageSwapTimeoutException_GivenFatalErrorMidflight.cs
+++ b/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowFirmwareInstallationImageSwapTimeoutException_GivenFatalErrorMidflight.cs
@@ -1,4 +1,5 @@
using FluentAssertions;
+using FluentAssertions.Extensions;
using Laerdal.McuMgr.Common.Enums;
using Laerdal.McuMgr.Common.Helpers;
using Laerdal.McuMgr.FirmwareInstaller.Contracts.Enums;
@@ -29,9 +30,7 @@ public async Task InstallAsync_ShouldThrowFirmwareInstallationImageSwapTimeoutEx
));
// Assert
- await work.Should()
- .ThrowExactlyAsync()
- .WithTimeoutInMs(3_000);
+ await work.Should().ThrowWithinAsync(3_000.Milliseconds());
mockedNativeFirmwareInstallerProxy.CancelCalled.Should().BeFalse();
mockedNativeFirmwareInstallerProxy.DisconnectCalled.Should().BeFalse(); //00
diff --git a/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowFirmwareInstallationInternalErrorException_GivenErroneousNativeFirmwareInstaller.cs b/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowFirmwareInstallationInternalErrorException_GivenErroneousNativeFirmwareInstaller.cs
index aad3e4d2..a87daa92 100644
--- a/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowFirmwareInstallationInternalErrorException_GivenErroneousNativeFirmwareInstaller.cs
+++ b/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowFirmwareInstallationInternalErrorException_GivenErroneousNativeFirmwareInstaller.cs
@@ -1,4 +1,5 @@
using FluentAssertions;
+using FluentAssertions.Extensions;
using Laerdal.McuMgr.Common.Helpers;
using Laerdal.McuMgr.FirmwareInstaller.Contracts.Enums;
using Laerdal.McuMgr.FirmwareInstaller.Contracts.Exceptions;
@@ -26,9 +27,7 @@ public async Task InstallAsync_ShouldThrowFirmwareInstallationInternalErrorExcep
// Assert
(
- await work.Should()
- .ThrowExactlyAsync()
- .WithTimeoutInMs(1_000)
+ await work.Should().ThrowWithinAsync(1_000.Milliseconds())
).WithInnerExceptionExactly("native symbols not loaded blah blah");
mockedNativeFirmwareInstallerProxy.CancelCalled.Should().BeFalse();
diff --git a/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowFirmwareInstallationTimeoutException_GivenTooSmallTimeout.cs b/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowFirmwareInstallationTimeoutException_GivenTooSmallTimeout.cs
index f2b3895f..d0aaed7d 100644
--- a/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowFirmwareInstallationTimeoutException_GivenTooSmallTimeout.cs
+++ b/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowFirmwareInstallationTimeoutException_GivenTooSmallTimeout.cs
@@ -31,9 +31,7 @@ public async Task InstallAsync_ShouldThrowFirmwareInstallationTimeoutException_G
));
// Assert
- await work.Should()
- .ThrowExactlyAsync()
- .WithTimeoutInMs((int)2.Seconds().TotalMilliseconds);
+ await work.Should().ThrowWithinAsync(2.Seconds());
mockedNativeFirmwareInstallerProxy.CancelCalled.Should().BeFalse();
mockedNativeFirmwareInstallerProxy.DisconnectCalled.Should().BeFalse(); //00
diff --git a/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowInstallationCancelledException_GivenCancellationRequestMidflight.cs b/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowInstallationCancelledException_GivenCancellationRequestMidflight.cs
index f5b81077..b499723e 100644
--- a/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowInstallationCancelledException_GivenCancellationRequestMidflight.cs
+++ b/Laerdal.McuMgr.Tests/FirmwareInstaller/FirmwareInstallerTestbed.InstallAsync.ShouldThrowInstallationCancelledException_GivenCancellationRequestMidflight.cs
@@ -40,9 +40,7 @@ public async Task InstallAsync_ShouldThrowFirmwareInstallationCancelledException
));
// Assert
- await work.Should()
- .ThrowExactlyAsync()
- .WithTimeoutInMs((int)5.Seconds().TotalMilliseconds);
+ await work.Should().ThrowWithinAsync(5.Seconds());
mockedNativeFirmwareInstallerProxy.CancelCalled.Should().BeTrue();
mockedNativeFirmwareInstallerProxy.DisconnectCalled.Should().BeFalse(); //00
diff --git a/Laerdal.McuMgr.slnx.DotSettings.user b/Laerdal.McuMgr.slnx.DotSettings.user
index e71543da..8e5f73b5 100644
--- a/Laerdal.McuMgr.slnx.DotSettings.user
+++ b/Laerdal.McuMgr.slnx.DotSettings.user
@@ -1,8 +1,10 @@

ForceIncluded
+ ForceIncluded
+ ForceIncluded
- <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
- <Solution />
+ <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
+ <Solution />
</SessionState>
diff --git a/Laerdal.McuMgr/Laerdal.McuMgr.csproj b/Laerdal.McuMgr/Laerdal.McuMgr.csproj
index 66691306..ed535d2b 100644
--- a/Laerdal.McuMgr/Laerdal.McuMgr.csproj
+++ b/Laerdal.McuMgr/Laerdal.McuMgr.csproj
@@ -11,16 +11,16 @@
- $(TargetFrameworks)netstandard2.1;
+ $(TargetFrameworks)net8.0;
$(TargetFrameworks)net8.0-android;
$(TargetFrameworks)net8.0-ios11;
$(TargetFrameworks)net8.0-maccatalyst
- true
- true
- true
- true
- true
+ true
+ true
+ true
+ true
+ true
true
true
@@ -71,10 +71,10 @@
$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
- 1.0.1177.0
- 1.0.1177.0
- 1.0.1177.0
- 1.0.1177.0
+ 1.0.1092.0
+ 1.0.1092.0
+ 1.0.1092.0
+ 1.0.1092.0
$(PackageId)
$(Authors)
@@ -159,7 +159,7 @@
-
+
@@ -167,21 +167,21 @@
-
+
-
+
-
+
-
-
+
+
diff --git a/Laerdal.McuMgr/Shared/Common/Helpers/TaskCompletionSourceExtensions.cs b/Laerdal.McuMgr/Shared/Common/Helpers/TaskCompletionSourceExtensions.cs
new file mode 100644
index 00000000..d2457270
--- /dev/null
+++ b/Laerdal.McuMgr/Shared/Common/Helpers/TaskCompletionSourceExtensions.cs
@@ -0,0 +1,502 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Laerdal.McuMgr.Common.Helpers
+{
+ ///
+ /// A family of extension methods that ensure that the is properly cleaned up in the case of a timeout either
+ /// from the cancellation token or the timeout specified.
+ ///
+ /// Always bear in mind that if the is never completed, its task will never complete. Even though the underlying
+ /// Task is not actually in a "scheduler" (since TCS tasks are Promise Tasks)
+ /// never completing tasks, of any type, is generally considered a bug.
+ ///
+ public static class TaskCompletionSourceExtensions
+ {
+ ///
+ /// Sets up a timeout-monitor on the given task. This is essentially a wrapper around
+ /// with two major differences:
+ /// - If the timeout is zero or negative then it gets interpreted as "wait forever" and the method will just return the task itself.
+ /// - Most importantly, in the case of a TimeoutException this method makes sure to properly cleanup
+ /// (cancel) the task itself so that you won't have to (it's so easy to forget this and it's a common source of memory-leaks and zombie-promise-tasks
+ /// that can cripple the system!).
+ ///
+ /// The task to monitor with a timeout.
+ /// The timeout in milliseconds. If zero or negative it gets interpreted as "wait forever" and the method will
+ /// just return the task itself.
+ /// (optional) The cancellation token that co-monitors the waiting mechanism.
+ /// Thrown when the task didn't complete within the specified timeout.
+ /// The hybridized task that you can await on if you have provided a positive timeout. The task itself otherwise.
+ ///
+ /// // per https://devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/
+ /// var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ ///
+ /// try
+ /// {
+ /// PropertyChanged += MyEventHandler_;
+ /// await tcs.WaitTaskWithOptionalTimeoutAsync(timeout);
+ /// }
+ /// finally
+ /// {
+ /// PropertyChanged -= MyEventHandler_; //this is needed in the case of a timeout
+ /// }
+ ///
+ /// return;
+ ///
+ /// void MyEventHandler_(object? _, SomeEventArgs ea_)
+ /// {
+ /// try
+ /// {
+ /// // ... logic here ...
+ ///
+ /// PropertyChanged -= BindableObject_PropertyChanged_; //best to unwire the listener as soon as we can
+ /// tcs.TrySetResult();
+ /// }
+ /// catch (Exception ex)
+ /// {
+ /// PropertyChanged -= BindableObject_PropertyChanged_; //best to unwire the listener as soon as we can
+ /// tcs.TrySetException(ex); //vital we need to ensure that the task gets completed one way or another
+ /// }
+ /// }
+ ///
+ public static Task WaitTaskWithOptionalTimeoutAsync(this TaskCompletionSource tcs, int timeoutInMilliseconds, CancellationToken cancellationToken = default)
+ {
+ return timeoutInMilliseconds <= 0
+ ? tcs.Task
+ : tcs.WaitTaskWithTimeoutAsync(TimeSpan.FromMilliseconds(timeoutInMilliseconds), cancellationToken);
+ }
+
+ ///
+ /// Sets up a timeout-monitor on the given task. This is essentially a wrapper around
+ /// with two major differences:
+ /// - If the timeout is zero or negative then it gets interpreted as "wait forever" and the method will just return the task itself.
+ /// - Most importantly, in the case of a TimeoutException this method makes sure to properly cleanup
+ /// (cancel) the task itself so that you won't have to (it's so easy to forget this and it's a common source of memory-leaks and zombie-promise-tasks
+ /// that can cripple the system!)
+ ///
+ /// The task to monitor with a timeout.
+ /// The amount of time to wait for before throwing a . If zero or negative it gets interpreted
+ /// as "wait forever" and the method will just return the task itself.
+ /// (optional) The cancellation token that co-monitors the waiting mechanism.
+ /// Thrown when the task didn't complete within the specified timeout.
+ /// The hybridized task that you can await on if you have provided a positive timeout. The task itself otherwise.
+ ///
+ /// // per https://devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/
+ /// var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ ///
+ /// try
+ /// {
+ /// PropertyChanged += MyEventHandler_;
+ /// await tcs.WaitTaskWithOptionalTimeoutAsync(timeout);
+ /// }
+ /// finally
+ /// {
+ /// PropertyChanged -= MyEventHandler_; //this is needed in the case of a timeout
+ /// }
+ ///
+ /// return;
+ ///
+ /// void MyEventHandler_(object? _, SomeEventArgs ea_)
+ /// {
+ /// try
+ /// {
+ /// // ... logic here ...
+ ///
+ /// PropertyChanged -= BindableObject_PropertyChanged_; //best to unwire the listener as soon as we can
+ /// tcs.TrySetResult();
+ /// }
+ /// catch (Exception ex)
+ /// {
+ /// PropertyChanged -= BindableObject_PropertyChanged_; //best to unwire the listener as soon as we can
+ /// tcs.TrySetException(ex); //vital we need to ensure that the task gets completed one way or another
+ /// }
+ /// }
+ ///
+ public static Task WaitTaskWithOptionalTimeoutAsync(this TaskCompletionSource tcs, TimeSpan timespan, CancellationToken cancellationToken = default)
+ {
+ return timespan <= TimeSpan.Zero
+ ? tcs.Task
+ : tcs.WaitTaskWithTimeoutAsync(timespan, cancellationToken);
+ }
+
+ ///
+ /// Sets up a timeout-monitor on the given task. This is essentially a wrapper around
+ /// with two major differences:
+ /// - If the timeout is negative you will get an thrown (WaitAsync() allows -1 as a special-case for "wait forever"
+ /// but this method doesn't allow that special case)
+ /// - Most importantly, in the case of a TimeoutException this method makes sure to properly cleanup
+ /// (cancel) the task itself so that you won't have to (it's so easy to forget this and it's a common source of memory-leaks and zombie-promise-tasks
+ /// that can cripple the system!).
+ ///
+ /// The task to monitor with a timeout.
+ /// The timeout in milliseconds. If zero or negative it gets interpreted as "wait forever" and the method will
+ /// just return the task itself.
+ /// (optional) The cancellation token that co-monitors the waiting mechanism.
+ /// Thrown when the task didn't complete within the specified timeout.
+ /// The hybridized task that you can await on.
+ ///
+ /// // per https://devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/
+ /// var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ ///
+ /// try
+ /// {
+ /// PropertyChanged += MyEventHandler_;
+ /// await tcs.WaitTaskWithOptionalTimeoutAsync(timeout);
+ /// }
+ /// finally
+ /// {
+ /// PropertyChanged -= MyEventHandler_; //this is needed in the case of a timeout
+ /// }
+ ///
+ /// return;
+ ///
+ /// void MyEventHandler_(object? _, SomeEventArgs ea_)
+ /// {
+ /// try
+ /// {
+ /// // ... logic here ...
+ ///
+ /// PropertyChanged -= BindableObject_PropertyChanged_; //best to unwire the listener as soon as we can
+ /// tcs.TrySetResult();
+ /// }
+ /// catch (Exception ex)
+ /// {
+ /// PropertyChanged -= BindableObject_PropertyChanged_; //best to unwire the listener as soon as we can
+ /// tcs.TrySetException(ex); //vital we need to ensure that the task gets completed one way or another
+ /// }
+ /// }
+ ///
+ public static Task WaitTaskWithTimeoutAsync(this TaskCompletionSource tcs, int timeoutInMilliseconds, CancellationToken cancellationToken = default)
+ {
+ return tcs.WaitTaskWithTimeoutAsync(TimeSpan.FromMilliseconds(timeoutInMilliseconds), cancellationToken);
+ }
+
+ ///
+ /// Sets up a timeout-monitor on the given task. This is essentially a wrapper around
+ /// with two major differences:
+ /// - If the timeout is negative you will get an thrown (WaitAsync() allows -1 as a special-case for "wait forever"
+ /// but this method doesn't allow that special case)
+ /// - Most importantly, in the case of a TimeoutException this method makes sure to properly cleanup
+ /// (cancel) the task itself so that you won't have to (it's so easy to forget this and it's a common source of memory-leaks and zombie-promise-tasks
+ /// that can cripple the system!).
+ ///
+ /// The task to monitor with a timeout.
+ /// The amount of time to wait for before throwing a . If zero or negative it gets interpreted
+ /// as "wait forever" and the method will just return the task itself.
+ /// (optional) The cancellation token that co-monitors the waiting mechanism.
+ /// Thrown when the task didn't complete within the specified timeout.
+ /// The hybridized task that you can await on.
+ ///
+ /// // per https://devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/
+ /// var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ ///
+ /// try
+ /// {
+ /// PropertyChanged += MyEventHandler_;
+ /// await tcs.WaitTaskWithTimeoutAsync(timeout);
+ /// }
+ /// finally
+ /// {
+ /// PropertyChanged -= MyEventHandler_; //this is needed in the case of a timeout
+ /// }
+ ///
+ /// return;
+ ///
+ /// void MyEventHandler_(object? _, SomeEventArgs ea_)
+ /// {
+ /// try
+ /// {
+ /// // ... logic here ...
+ ///
+ /// PropertyChanged -= BindableObject_PropertyChanged_; //best to unwire the listener as soon as we can
+ /// tcs.TrySetResult();
+ /// }
+ /// catch (Exception ex)
+ /// {
+ /// PropertyChanged -= BindableObject_PropertyChanged_; //best to unwire the listener as soon as we can
+ /// tcs.TrySetException(ex); //vital we need to ensure that the task gets completed one way or another
+ /// }
+ /// }
+ ///
+ public async static Task WaitTaskWithTimeoutAsync(this TaskCompletionSource tcs, TimeSpan timespan, CancellationToken cancellationToken = default)
+ {
+ if (timespan < TimeSpan.Zero) //note that this deviates from the behaviour of .WaitAsync() which does accept -1 milliseconds which means "wait forever"
+ {
+ var exception = new ArgumentException("Timeout must be zero or positive", nameof(timespan));
+ tcs.TrySetException(exception); //vital we need to ensure that the task gets completed one way or another
+
+ throw exception;
+ }
+
+ try
+ {
+ await tcs.Task.WaitAsync(timespan, cancellationToken);
+ }
+ catch (Exception ex) when (ex is TimeoutException or TaskCanceledException) //taskcanceledexception can come from cancellation-token timeouts
+ {
+ var isCancellationSuccessful = tcs.TrySetCanceled(cancellationToken); //00 vital
+ if (isCancellationSuccessful)
+ throw;
+
+ if (tcs.Task.IsCompletedSuccessfully) //10 barely completed in time
+ return; //micro-optimization to avoid the overhead of await
+
+ await tcs.Task;
+ }
+
+ //00 it is absolutely vital to trash the tcs and ensure it will not pester the system as a zombie promise-task
+ // waitasync() does not take care of this aspect because quite simply it cannot even if it tried
+ // all waitasync() does is to simply unwire the continuation from task.Task and leave it be
+ //
+ //10 if the cancellation fails it means one of the following two things:
+ //
+ // 1. that the task barely managed to complete in time on its own at the nick of time we prefer to give
+ // it the benefit of the doubt and let it complete normally
+ //
+ // 2. that the task itself threw a timeout-exception and in this case we prefer to honor the exception that
+ // the task itself threw and let it be propagated to the caller
+ }
+
+ ///
+ /// Sets up a timeout-monitor on the given task. This is essentially a wrapper around
+ /// with two major differences:
+ /// - If the timeout is zero or negative then it gets interpreted as "wait forever" and the method will just return the task itself.
+ /// - Most importantly, in the case of a TimeoutException this method makes sure to properly cleanup
+ /// (cancel) the task itself so that you won't have to (it's so easy to forget this and it's a common source of memory-leaks and zombie-promise-tasks
+ /// that can cripple the system!).
+ ///
+ /// The task to monitor with a timeout.
+ /// The timeout in milliseconds. If zero or negative it gets interpreted as "wait forever" and the method
+ /// will just return the task itself.
+ /// (optional) The cancellation token that co-monitors the waiting mechanism.
+ /// Thrown when the task didn't complete within the specified timeout.
+ /// The hybridized task that you can await on if you have provided a positive timeout. The task itself otherwise.
+ ///
+ /// // per https://devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/
+ /// var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ ///
+ /// try
+ /// {
+ /// PropertyChanged += MyEventHandler_;
+ /// await tcs.WaitTaskWithTimeoutAsync<int>(timeout);
+ /// }
+ /// finally
+ /// {
+ /// PropertyChanged -= MyEventHandler_; //this is needed in the case of a timeout
+ /// }
+ ///
+ /// return;
+ ///
+ /// void MyEventHandler_(object? _, SomeEventArgs ea_)
+ /// {
+ /// try
+ /// {
+ /// // ... logic here ...
+ ///
+ /// PropertyChanged -= BindableObject_PropertyChanged_; //best to unwire the listener as soon as we can
+ /// tcs.TrySetResult(123);
+ /// }
+ /// catch (Exception ex)
+ /// {
+ /// PropertyChanged -= BindableObject_PropertyChanged_; //best to unwire the listener as soon as we can
+ /// tcs.TrySetException(ex); //vital we need to ensure that the task gets completed one way or another
+ /// }
+ /// }
+ ///
+ public static Task WaitTaskWithOptionalTimeoutAsync(this TaskCompletionSource tcs, int timeoutInMilliseconds, CancellationToken cancellationToken = default)
+ {
+ return timeoutInMilliseconds <= 0
+ ? tcs.Task
+ : tcs.WaitTaskWithTimeoutAsync(TimeSpan.FromMilliseconds(timeoutInMilliseconds), cancellationToken);
+ }
+
+ ///
+ /// Sets up a timeout-monitor on the given task. This is essentially a wrapper around
+ /// with two major differences:
+ /// - If the timeout is zero or negative then it gets interpreted as "wait forever" and the method will just return the task itself.
+ /// - Most importantly, in the case of a TimeoutException this method makes sure to properly cleanup
+ /// (cancel) the task itself so that you won't have to (it's so easy to forget this and it's a common source of memory-leaks and zombie-promise-tasks
+ /// that can cripple the system!).
+ ///
+ /// The task to monitor with a timeout.
+ /// The amount of time to wait for before throwing a . If zero or negative it gets interpreted
+ /// as "wait forever" and the method will just return the task itself.
+ /// (optional) The cancellation token that co-monitors the waiting mechanism.
+ /// Thrown when the task didn't complete within the specified timeout.
+ /// The hybridized task that you can await on if you have provided a positive timeout. The task itself otherwise.
+ ///
+ /// // per https://devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/
+ /// var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ ///
+ /// try
+ /// {
+ /// PropertyChanged += MyEventHandler_;
+ /// await tcs.WaitTaskWithOptionalTimeoutAsync<int>(timeout);
+ /// }
+ /// finally
+ /// {
+ /// PropertyChanged -= MyEventHandler_; //this is needed in the case of a timeout
+ /// }
+ ///
+ /// return;
+ ///
+ /// void MyEventHandler_(object? _, SomeEventArgs ea_)
+ /// {
+ /// try
+ /// {
+ /// // ... logic here ...
+ ///
+ /// PropertyChanged -= BindableObject_PropertyChanged_; //best to unwire the listener as soon as we can
+ /// tcs.TrySetResult(123);
+ /// }
+ /// catch (Exception ex)
+ /// {
+ /// PropertyChanged -= BindableObject_PropertyChanged_; //best to unwire the listener as soon as we can
+ /// tcs.TrySetException(ex); //vital we need to ensure that the task gets completed one way or another
+ /// }
+ /// }
+ ///
+ public static Task WaitTaskWithOptionalTimeoutAsync(this TaskCompletionSource tcs, TimeSpan timespan, CancellationToken cancellationToken = default)
+ {
+ return timespan <= TimeSpan.Zero
+ ? tcs.Task
+ : tcs.WaitTaskWithTimeoutAsync(timespan, cancellationToken);
+ }
+
+ ///
+ /// Sets up a timeout-monitor on the given task. This is essentially a wrapper around
+ /// with two major differences:
+ /// - If the timeout is negative you will get an thrown (WaitAsync() allows -1 as a special-case for "wait forever"
+ /// but this method doesn't allow that special case)
+ /// - Most importantly, in the case of a TimeoutException this method makes sure to properly cleanup
+ /// (cancel) the task itself so that you won't have to (it's so easy to forget this and it's a common source of memory-leaks and zombie-promise-tasks
+ /// that can cripple the system!).
+ ///
+ /// The task to monitor with a timeout.
+ /// The timeout in milliseconds. If zero or negative it gets interpreted as "wait forever" and the method will
+ /// just return the task itself.
+ /// (optional) The cancellation token that co-monitors the waiting mechanism.
+ /// Thrown when the task didn't complete within the specified timeout.
+ ///
+ /// // per https://devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/
+ /// var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ ///
+ /// try
+ /// {
+ /// PropertyChanged += MyEventHandler_;
+ /// await tcs.WaitTaskWithTimeoutAsync<int>(timeout);
+ /// }
+ /// finally
+ /// {
+ /// PropertyChanged -= MyEventHandler_; //this is needed in the case of a timeout
+ /// }
+ ///
+ /// return;
+ ///
+ /// void MyEventHandler_(object? _, SomeEventArgs ea_)
+ /// {
+ /// try
+ /// {
+ /// // ... logic here ...
+ ///
+ /// PropertyChanged -= BindableObject_PropertyChanged_; //best to unwire the listener as soon as we can
+ /// tcs.TrySetResult(123);
+ /// }
+ /// catch (Exception ex)
+ /// {
+ /// PropertyChanged -= BindableObject_PropertyChanged_; //best to unwire the listener as soon as we can
+ /// tcs.TrySetException(ex); //vital we need to ensure that the task gets completed one way or another
+ /// }
+ /// }
+ ///
+ public static Task WaitTaskWithTimeoutAsync(this TaskCompletionSource tcs, int timeoutInMilliseconds, CancellationToken cancellationToken = default)
+ {
+ return tcs.WaitTaskWithTimeoutAsync(TimeSpan.FromMilliseconds(timeoutInMilliseconds), cancellationToken);
+ }
+
+ ///
+ /// Sets up a timeout-monitor on the given task. This is essentially a wrapper around
+ /// with two major differences:
+ /// - If the timeout is negative you will get an thrown (WaitAsync() allows -1 as a special-case for "wait forever"
+ /// but this method doesn't allow that special case)
+ /// - Most importantly, in the case of a TimeoutException this method makes sure to properly cleanup
+ /// (cancel) the task itself so that you won't have to (it's so easy to forget this and it's a common source of memory-leaks and zombie-promise-tasks
+ /// that can cripple the system!).
+ ///
+ /// The task to monitor with a timeout.
+ /// The amount of time to wait for before throwing a . If zero or negative it gets interpreted
+ /// as "wait forever" and the method will just return the task itself.
+ /// (optional) The cancellation token that co-monitors the waiting mechanism.
+ /// Thrown when the task didn't complete within the specified timeout.
+ ///
+ /// // per https://devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/
+ /// var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ ///
+ /// try
+ /// {
+ /// PropertyChanged += MyEventHandler_;
+ /// await tcs.WaitTaskWithTimeoutAsync<int>(timeout);
+ /// }
+ /// finally
+ /// {
+ /// PropertyChanged -= MyEventHandler_; //this is needed in the case of a timeout
+ /// }
+ ///
+ /// return;
+ ///
+ /// void MyEventHandler_(object? _, SomeEventArgs ea_)
+ /// {
+ /// try
+ /// {
+ /// // ... logic here ...
+ ///
+ /// PropertyChanged -= BindableObject_PropertyChanged_; //best to unwire the listener as soon as we can
+ /// tcs.TrySetResult(123);
+ /// }
+ /// catch (Exception ex)
+ /// {
+ /// PropertyChanged -= BindableObject_PropertyChanged_; //best to unwire the listener as soon as we can
+ /// tcs.TrySetException(ex); //vital we need to ensure that the task gets completed one way or another
+ /// }
+ /// }
+ ///
+ public async static Task WaitTaskWithTimeoutAsync(this TaskCompletionSource tcs, TimeSpan timespan, CancellationToken cancellationToken = default)
+ {
+ if (timespan < TimeSpan.Zero) //note that this deviates from the behaviour of .WaitAsync() which does accept -1 milliseconds which means "wait forever"
+ {
+ var exception = new ArgumentException("Timeout must be zero or positive", nameof(timespan));
+ tcs.TrySetException(exception); //vital we need to ensure that the task gets completed one way or another
+
+ throw exception;
+ }
+
+ try
+ {
+ return await tcs.Task.WaitAsync(timespan, cancellationToken);
+ }
+ catch (Exception ex) when (ex is TimeoutException or TaskCanceledException) //taskcanceledexception can come from cancellation-token timeouts
+ {
+ var isCancellationSuccessful = tcs.TrySetCanceled(cancellationToken); //00 vital
+ if (isCancellationSuccessful)
+ throw;
+
+ return tcs.Task.IsCompletedSuccessfully //10 barely completed in time
+ ? tcs.Task.Result //micro-optimization to avoid the overhead of await
+ : await tcs.Task; //this means the task itself faulted
+ }
+
+ //00 it is absolutely vital to trash the tcs and ensure it will not pester the system as a zombie promise-task
+ // waitasync() does not take care of this aspect because quite simply it cannot even if it tried
+ // all waitasync() does is to simply unwire the continuation from task.Task and leave it be
+ //
+ //10 if the cancellation fails it means one of the following two things:
+ //
+ // 1. that the task barely managed to complete in time on its own at the nick of time we prefer to give
+ // it the benefit of the doubt and let it complete normally
+ //
+ // 2. that the task itself threw a timeout-exception and in this case we prefer to honor the exception that
+ // the task itself threw and let it be propagated to the caller
+ }
+ }
+}
\ No newline at end of file
diff --git a/Laerdal.McuMgr/Shared/Common/Helpers/TaskExtensions.cs b/Laerdal.McuMgr/Shared/Common/Helpers/TaskExtensions.cs
deleted file mode 100644
index 67e54ba0..00000000
--- a/Laerdal.McuMgr/Shared/Common/Helpers/TaskExtensions.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System;
-using System.Threading.Tasks;
-
-namespace Laerdal.McuMgr.Common.Helpers
-{
- static internal class TaskExtensions
- {
- static public async Task WithTimeoutInMs(this Task task, int timeout)
- {
- if (await Task.WhenAny(task, Task.Delay(timeout)) == task)
- return await task;
-
- throw new TimeoutException();
- }
- }
-}
\ No newline at end of file
diff --git a/Laerdal.McuMgr/Shared/DeviceResetter/DeviceResetter.cs b/Laerdal.McuMgr/Shared/DeviceResetter/DeviceResetter.cs
index ff40e62f..78f98c07 100644
--- a/Laerdal.McuMgr/Shared/DeviceResetter/DeviceResetter.cs
+++ b/Laerdal.McuMgr/Shared/DeviceResetter/DeviceResetter.cs
@@ -109,9 +109,7 @@ public async Task ResetAsync(int timeoutInMs = -1)
if (verdict != EDeviceResetterInitializationVerdict.Success)
throw new ArgumentException(verdict.ToString());
- _ = timeoutInMs <= 0
- ? await taskCompletionSource.Task
- : await taskCompletionSource.Task.WithTimeoutInMs(timeout: timeoutInMs);
+ await taskCompletionSource.WaitTaskWithOptionalTimeoutAsync(timeoutInMs);
}
catch (TimeoutException ex)
{
diff --git a/Laerdal.McuMgr/Shared/FileDownloader/FileDownloader.cs b/Laerdal.McuMgr/Shared/FileDownloader/FileDownloader.cs
index 737be86a..61df02e5 100644
--- a/Laerdal.McuMgr/Shared/FileDownloader/FileDownloader.cs
+++ b/Laerdal.McuMgr/Shared/FileDownloader/FileDownloader.cs
@@ -285,10 +285,7 @@ public async Task DownloadAsync(
if (verdict != EFileDownloaderVerdict.Success)
throw new ArgumentException(verdict.ToString());
- result = timeoutForDownloadInMs < 0
- ? await taskCompletionSource.Task
- : await taskCompletionSource.Task.WithTimeoutInMs(timeout: timeoutForDownloadInMs);
-
+ result = await taskCompletionSource.WaitTaskWithOptionalTimeoutAsync(timeoutForDownloadInMs);
break;
}
catch (TimeoutException ex)
diff --git a/Laerdal.McuMgr/Shared/FileUploader/FileUploader.cs b/Laerdal.McuMgr/Shared/FileUploader/FileUploader.cs
index cf822540..7ab52403 100644
--- a/Laerdal.McuMgr/Shared/FileUploader/FileUploader.cs
+++ b/Laerdal.McuMgr/Shared/FileUploader/FileUploader.cs
@@ -352,10 +352,7 @@ public async Task UploadAsync(
if (verdict != EFileUploaderVerdict.Success)
throw new ArgumentException(verdict.ToString());
- _ = timeoutForUploadInMs < 0
- ? await taskCompletionSource.Task
- : await taskCompletionSource.Task.WithTimeoutInMs(timeout: timeoutForUploadInMs); //order
-
+ await taskCompletionSource.WaitTaskWithOptionalTimeoutAsync(timeoutForUploadInMs); //order
break;
}
catch (TimeoutException ex)
diff --git a/Laerdal.McuMgr/Shared/FirmwareEraser/FirmwareEraser.cs b/Laerdal.McuMgr/Shared/FirmwareEraser/FirmwareEraser.cs
index b53d2d2d..ba9d5ba4 100644
--- a/Laerdal.McuMgr/Shared/FirmwareEraser/FirmwareEraser.cs
+++ b/Laerdal.McuMgr/Shared/FirmwareEraser/FirmwareEraser.cs
@@ -125,9 +125,7 @@ public async Task EraseAsync(int imageIndex = 1, int timeoutInMs = -1)
if (verdict != EFirmwareErasureInitializationVerdict.Success)
throw new ArgumentException(verdict.ToString());
- _ = timeoutInMs <= 0
- ? await taskCompletionSource.Task
- : await taskCompletionSource.Task.WithTimeoutInMs(timeout: timeoutInMs);
+ await taskCompletionSource.WaitTaskWithOptionalTimeoutAsync(timeoutInMs);
}
catch (TimeoutException ex)
{
diff --git a/Laerdal.McuMgr/Shared/FirmwareInstaller/Contracts/Exceptions/FirmwareInstallationUploadingStageErroredOutException.cs b/Laerdal.McuMgr/Shared/FirmwareInstaller/Contracts/Exceptions/FirmwareInstallationUploadingStageErroredOutException.cs
index 4d728360..d15254bd 100644
--- a/Laerdal.McuMgr/Shared/FirmwareInstaller/Contracts/Exceptions/FirmwareInstallationUploadingStageErroredOutException.cs
+++ b/Laerdal.McuMgr/Shared/FirmwareInstaller/Contracts/Exceptions/FirmwareInstallationUploadingStageErroredOutException.cs
@@ -4,8 +4,8 @@ namespace Laerdal.McuMgr.FirmwareInstaller.Contracts.Exceptions
{
public class FirmwareInstallationUploadingStageErroredOutException : FirmwareInstallationErroredOutException, IFirmwareInstallationException
{
- public FirmwareInstallationUploadingStageErroredOutException(EGlobalErrorCode globalErrorCode)
- : base("An error occurred while uploading the firmware", globalErrorCode)
+ public FirmwareInstallationUploadingStageErroredOutException(string internalErrorMessage, EGlobalErrorCode globalErrorCode)
+ : base($"An error occurred while uploading the firmware: {internalErrorMessage}", globalErrorCode)
{
}
}
diff --git a/Laerdal.McuMgr/Shared/FirmwareInstaller/FirmwareInstaller.cs b/Laerdal.McuMgr/Shared/FirmwareInstaller/FirmwareInstaller.cs
index b80a7254..f4d558b0 100644
--- a/Laerdal.McuMgr/Shared/FirmwareInstaller/FirmwareInstaller.cs
+++ b/Laerdal.McuMgr/Shared/FirmwareInstaller/FirmwareInstaller.cs
@@ -266,9 +266,7 @@ public async Task InstallAsync(
if (verdict != EFirmwareInstallationVerdict.Success)
throw new ArgumentException(verdict.ToString());
- _ = timeoutInMs <= 0
- ? await taskCompletionSource.Task
- : await taskCompletionSource.Task.WithTimeoutInMs(timeout: timeoutInMs);
+ await taskCompletionSource.WaitTaskWithOptionalTimeoutAsync(timeoutInMs);
}
catch (TimeoutException ex)
{
@@ -373,7 +371,7 @@ void FirmwareInstaller_FatalErrorOccurred_(object _, FatalErrorOccurredEventArgs
=> new FirmwareInstallationConfirmationStageTimeoutException(estimatedSwapTimeInMilliseconds, ea_.GlobalErrorCode),
{ FatalErrorType: EFirmwareInstallerFatalErrorType.FirmwareUploadingErroredOut } or { State: EFirmwareInstallationState.Uploading }
- => new FirmwareInstallationUploadingStageErroredOutException(ea_.GlobalErrorCode),
+ => new FirmwareInstallationUploadingStageErroredOutException(ea_.ErrorMessage, ea_.GlobalErrorCode),
_ => new FirmwareInstallationErroredOutException($"{ea_.ErrorMessage} (state={ea_.State})", ea_.GlobalErrorCode)
});
diff --git a/Laerdal.Scripts/Laerdal.GenerateSignAndUploadSbom.sh b/Laerdal.Scripts/Laerdal.GenerateSignAndUploadSbom.sh
index f073f71d..3d918ad4 100644
--- a/Laerdal.Scripts/Laerdal.GenerateSignAndUploadSbom.sh
+++ b/Laerdal.Scripts/Laerdal.GenerateSignAndUploadSbom.sh
@@ -2,6 +2,9 @@
# set -x
+declare host_os=""
+declare host_os_and_architecture=""
+
declare project_name=""
declare project_version=""
@@ -13,6 +16,8 @@ declare csproj_classifier=""
declare output_directory_path=""
declare output_sbom_file_name=""
declare sbom_signing_key_file_path=""
+declare skip_installing_cyclonedx_cli_tool="no"
+declare skip_installing_cyclonedx_dotnet_extension="no"
declare dependency_tracker_url=""
declare dependency_tracker_api_key_file_path=""
@@ -21,17 +26,17 @@ function parse_arguments() {
while [[ $# -gt 0 ]]; do
case $1 in
-
+
--project-name)
project_name="$2"
shift
;;
-
+
--project-version)
project_version="$2"
shift
;;
-
+
--parent-project-name)
parent_project_name="$2"
shift
@@ -56,7 +61,7 @@ function parse_arguments() {
output_directory_path="$2"
shift
;;
-
+
--output-sbom-file-name)
output_sbom_file_name="$2"
shift
@@ -67,6 +72,16 @@ function parse_arguments() {
shift
;;
+ --skip-installing-cyclonedx-cli-tool)
+ skip_installing_cyclonedx_cli_tool="yes"
+ # shift # no need to shift here
+ ;;
+
+ --skip-installing-cyclonedx-dotnet-extension)
+ skip_installing_cyclonedx_dotnet_extension="yes"
+ # shift # no need to shift here
+ ;;
+
--dependency-tracker-url)
dependency_tracker_url="$2"
shift
@@ -104,11 +119,11 @@ function parse_arguments() {
# if [[ -z ${parent_project_version} ]]; then this is optional
# ...
-
+
# if [[ -n ${parent_project_name} && -z ${parent_project_version} ]]; then # nah better not to enforce this
# echo "Specifying --parent-project-version is mandatory when --parent-project-name has been used!"
# usage
- # exit 1
+ # exit 1
# fi
if [[ -z ${csproj_file_path} ]]; then
@@ -134,13 +149,13 @@ function parse_arguments() {
usage
exit 1
fi
-
+
if [[ -z ${sbom_signing_key_file_path} ]]; then
echo "Specifying --sbom-signing-key-file-path is mandatory!"
usage
exit 1
fi
-
+
if [[ -z ${dependency_tracker_url} ]]; then
echo "Specifying --dependency-tracker-url is mandatory!"
usage
@@ -157,125 +172,234 @@ function parse_arguments() {
function usage() {
local -r script_name=$(basename "$0")
- echo "Usage: ${script_name} --project-name --project-version [--parent-project-name --parent-project-version ] --csproj-file-path --csproj-file-path --output-directory-path --output-sbom-file-name --sbom-signing-key-file-path --dependency-tracker-url --dependency-tracker-api-key-file-path "
+ echo "Usage: ${script_name} --project-name --project-version [--skip-installing-cyclonedx-cli-tool] [--skip-installing-cyclonedx-dotnet-extension] [--parent-project-name --parent-project-version ] --csproj-file-path --csproj-file-path --output-directory-path --output-sbom-file-name --sbom-signing-key-file-path --dependency-tracker-url --dependency-tracker-api-key-file-path "
}
-function install_tools() {
+function sniff_and_validate_host_os_and_architecture() {
+ host_os="$(uname -s)"
+ case "${host_os}" in
+ Linux*) host_os="Linux" ;;
+ MINGW*) host_os="Windows" ;;
+ Darwin*) host_os="Mac" ;;
+ CYGWIN*) host_os="Windows" ;;
+ MSYS_NT*) host_os="Windows" ;;
+ esac
+
+ declare architecture=$(uname -m)
+ case ${architecture} in
+ i386) architecture="x86" ;;
+ i686) architecture="x86" ;;
+ x64) architecture="x64" ;; # shouldnt happen but just in case
+ x86_64) architecture="x64" ;;
+
+ armv7l) architecture="arm" ;;
+ arm64) architecture="arm64" ;;
+ aarch64) architecture="arm64" ;;
+ arm) dpkg --print-architecture | grep -q "arm64" && architecture="arm64" || architecture="arm" ;;
+ esac
+
+ host_os_and_architecture="${host_os}-${architecture}" # e.g. Linux-x64
+
+ if [[ ${host_os} == "Mac" ]] ||
+ [[ ${host_os} == "Linux" ]] ||
+ [[ ${host_os_and_architecture} == "Windows-x86" ]] ||
+ [[ ${host_os_and_architecture} == "Windows-x64" ]] ||
+ [[ ${host_os_and_architecture} == "Windows-arm" ]] ||
+ [[ ${host_os_and_architecture} == "Windows-arm64" ]]; then
+ return # host os is supported
+ fi
+
+ echo "Unsupported host OS '${host_os_and_architecture}' - don't know how to install the CycloneDX tool on this platform."
+ exit 1
+}
+
+function install_dotnet_cyclonedx() {
+ if [[ ${skip_installing_cyclonedx_dotnet_extension} == "yes" ]]; then
+ return # the calling environment might have τηε cyclonedx extension for dotnet preinstalled
+ fi
echo
echo "** Installing CycloneDX as a dotnet tool:"
- dotnet tool \
- install \
- --global CycloneDX
+ dotnet tool \
+ install \
+ --global CycloneDX
declare exitCode=$?
if [ $exitCode != 0 ]; then
- echo "##vso[task.logissue type=error]Something went wrong with the CycloneDX tool for dotnet."
+ echo "Something went wrong with the CycloneDX tool for dotnet."
exit 10
fi
echo
echo "** CycloneDX:"
- which dotnet-CycloneDX && dotnet-CycloneDX --version
+ which dotnet-CycloneDX && dotnet cyclonedx --version
declare exitCode=$?
if [ $exitCode != 0 ]; then
- echo "##vso[task.logissue type=error]Something's wrong with 'dotnet-CycloneDX'."
+ echo "Something's wrong with 'dotnet-CycloneDX'."
exit 12
fi
+}
- # we need to install the CycloneDX tool too in order to sign the artifacts
- curl --output cyclonedx --url https://github.com/CycloneDX/cyclonedx-cli/releases/download/v0.26.0/cyclonedx-osx-arm64 \
- && chmod +x cyclonedx
- declare exitCode=$?
- if [ $exitCode != 0 ]; then
- echo "##vso[task.logissue type=error]Failed to install 'cyclonedx'."
- exit 13
+function install_cyclonedx_standalone() { # we need to install the CycloneDX tool too in order to sign the artifacts
+ if [[ ${skip_installing_cyclonedx_cli_tool} == "yes" ]]; then
+ return # the calling environment might have cyclonedx preinstalled
fi
+ sniff_and_validate_host_os_and_architecture
+
+ echo "** Installing cyclonedx cli tool for '${host_os}'"
+ if [[ ${host_os} == "Mac" ]] || [[ ${host_os} == "Linux" ]]; then
+ brew install cyclonedx/cyclonedx/cyclonedx-cli # both the macos and linux vmimages support brew so we can use it
+ declare exitCode=$?
+ if [ $exitCode != 0 ]; then
+ echo "Failed to install 'cyclonedx'."
+ exit 1
+ fi
+
+ return
+ fi
+
+ if [[ ${host_os_and_architecture} == "Windows-x86" ]]; then # windows does not support brew and chocolatey does not have a cyclonedx-cli package as of Q3 2024
+ curl --output cyclonedx --url https://github.com/CycloneDX/cyclonedx-cli/releases/download/v0.27.1/cyclonedx-win-x86.exe &&
+ chmod +x cyclonedx
+ declare exitCode=$?
+ if [ $exitCode != 0 ]; then
+ echo "Failed to install 'cyclonedx'."
+ exit 1
+ fi
+
+ return
+ fi
+
+ if [[ ${host_os_and_architecture} == "Windows-x64" ]]; then
+ curl --output cyclonedx --url https://github.com/CycloneDX/cyclonedx-cli/releases/download/v0.27.1/cyclonedx-win-x64.exe &&
+ chmod +x cyclonedx
+ declare exitCode=$?
+ if [ $exitCode != 0 ]; then
+ echo "Failed to install 'cyclonedx'."
+ exit 1
+ fi
+
+ return
+ fi
+
+ if [[ ${host_os_and_architecture} == "Windows-arm" ]]; then
+ curl --output cyclonedx --url https://github.com/CycloneDX/cyclonedx-cli/releases/download/v0.27.1/cyclonedx-win-arm.exe &&
+ chmod +x cyclonedx
+ declare exitCode=$?
+ if [ $exitCode != 0 ]; then
+ echo "Failed to install 'cyclonedx'."
+ exit 10
+ fi
+
+ return
+ fi
+
+ if [[ ${host_os_and_architecture} == "Windows-arm64" ]]; then
+ curl --output cyclonedx --url https://github.com/CycloneDX/cyclonedx-cli/releases/download/v0.27.1/cyclonedx-win-arm64.exe &&
+ chmod +x cyclonedx
+ declare exitCode=$?
+ if [ $exitCode != 0 ]; then
+ echo "Failed to install 'cyclonedx'."
+ exit 10
+ fi
+
+ return
+ fi
+
+ echo "Unsupported host OS '${host_os_and_architecture}' - cannot install 'cyclonedx-cli'."
+ exit 1
+}
+
+function install_tools() {
+ install_dotnet_cyclonedx # order
+ install_cyclonedx_standalone # order
}
function generate_sign_and_upload_sbom() {
# set -x
- # GENERATE SBOM we intentionally disable package restore because the packages are already restored at this point
- dotnet-CycloneDX "${csproj_file_path}" \
- --exclude-dev \
- --disable-package-restore \
- --include-project-references \
- \
- --output "${output_directory_path}" \
- --set-type "${csproj_classifier}" \
- --set-version "${project_version}" \
- \
- --filename "${output_sbom_file_name}"
+ # GENERATE SBOM
+ dotnet cyclonedx "${csproj_file_path}" \
+ --exclude-dev \
+ --include-project-references \
+ \
+ --output "${output_directory_path}" \
+ --set-type "${csproj_classifier}" \
+ --set-version "${project_version}" \
+ \
+ --filename "${output_sbom_file_name}"
declare exitCode=$?
if [ ${exitCode} != 0 ]; then
- echo "##vso[task.logissue type=error]Failed to generate the SBOM!"
+ echo "Failed to generate the SBOM!"
exit 20
fi
-
-
# SIGN SBOM todo figure out why this doesnt actually sign anything on windows even though on macos it works as intended
declare -r bom_file_path="${output_directory_path}/${output_sbom_file_name}"
- ./cyclonedx sign bom \
- "${bom_file_path}" \
- --key-file "${sbom_signing_key_file_path}"
+
+ declare cyclonedxCliFilepath="./cyclonedx" # we prioritize the local installation of the cyclonedx cli tool but
+ if [[ ! -f "${cyclonedxCliFilepath}" ]]; then # if the local installation of the cyclonedx cli tool is not available
+ cyclonedxCliFilepath="cyclonedx" # then and only then we opt to use the global installation of the tool
+ fi
+
+ "${cyclonedxCliFilepath}" sign bom \
+ "${bom_file_path}" \
+ --key-file "${sbom_signing_key_file_path}"
declare exitCode=$?
if [ ${exitCode} != 0 ]; then
- echo "##vso[task.logissue type=error]Singing the SBOM failed!"
+ echo "Singing the SBOM failed!"
exit 30
fi
# echo -e "\n\n"
# tail "${bom_file_path}"
# echo -e "\n\n"
-
-
# UPLOAD SBOM
declare optional_parent_project_name_parameter=""
if [[ -n ${parent_project_name} ]]; then
- optional_parent_project_name_parameter="--form parentName=${parent_project_name}"
+ optional_parent_project_name_parameter="--form parentName=${parent_project_name}"
fi
-
+
declare optional_parent_project_version_parameter=""
if [[ -n ${parent_project_version} ]]; then
- optional_parent_project_version_parameter="--form parentVersion=${parent_project_version}"
+ optional_parent_project_version_parameter="--form parentVersion=${parent_project_version}"
fi
- declare -r http_response_code=$( \
- curl "${dependency_tracker_url}" \
- --location \
- --request "POST" \
- \
- --header "Content-Type: multipart/form-data" \
- --header "X-API-Key: $(cat "${dependency_tracker_api_key_file_path}")" \
- \
- --form "bom=@${bom_file_path}" \
- --form "autoCreate=true" \
- \
- --form "projectName=${project_name}" \
- --form "projectVersion=${project_version}" \
- \
- ${optional_parent_project_name_parameter} \
- ${optional_parent_project_version_parameter} \
- \
- -w "%{http_code}" \
+ declare -r http_response_code=$(
+ curl "${dependency_tracker_url}" \
+ --location \
+ --request "POST" \
+ \
+ --header "Content-Type: multipart/form-data" \
+ --header "X-API-Key: $(cat "${dependency_tracker_api_key_file_path}")" \
+ \
+ --form "bom=@${bom_file_path}" \
+ --form "autoCreate=true" \
+ \
+ --form "projectName=${project_name}" \
+ --form "projectVersion=${project_version}" \
+ \
+ ${optional_parent_project_name_parameter} \
+ ${optional_parent_project_version_parameter} \
+ \
+ -o /dev/null \
+ -w "%{http_code}"
)
- declare exitCode=$?
- set +x
-
+ # declare exitCode=$? # no point to get the exit code because we use a subshell to capture the http response code
+
echo "** Curl sbom-uploading HTTP Response Code: ${http_response_code}"
-
- if [ ${exitCode} != 0 ]; then
- echo "##vso[task.logissue type=error]SBOM Uploading failed!"
+
+ if [[ -z ${http_response_code} ]] || [[ ${http_response_code} -gt 299 ]]; then
+ echo "SBOM Uploading failed!"
exit 40
fi
}
function main() {
- parse_arguments "$@"
- install_tools
- generate_sign_and_upload_sbom
+ parse_arguments "$@" # order
+ install_tools # order
+ generate_sign_and_upload_sbom # order
}
main "$@"
+# set +x
\ No newline at end of file