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 22a122b2..8e5f73b5 100644 --- a/Laerdal.McuMgr.slnx.DotSettings.user +++ b/Laerdal.McuMgr.slnx.DotSettings.user @@ -1,5 +1,7 @@  ForceIncluded + ForceIncluded + ForceIncluded <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> <Solution /> 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) });