Implement help command.
MODIFY
WalletWasabi.Tests/HwiTests/HwiClientTests.cs
WalletWasabi.Tests/HwiTests/HwiClientTests.cs
@@ -11,6 +11,14 @@ namespace WalletWasabi.Tests.HwiTests
{
public class HwiClientTests
{
+ #region SharedVariables
+
+ public TimeSpan ReasonableRequestTimeout { get; } = TimeSpan.FromSeconds(3);
+
+ #endregion SharedVariables
+
+ #region Tests
+
[Theory]
[MemberData(nameof(GetDifferentNetworkValues))]
public void CanCreate(Network network)
@@ -18,6 +26,50 @@ namespace WalletWasabi.Tests.HwiTests
new HwiClient(network);
}
+ [Fact]
+ public void ConstructorThrowsArgumentNullException()
+ {
+ Assert.Throws<ArgumentNullException>(() => new HwiClient(null));
+ }
+
+ [Theory]
+ [MemberData(nameof(GetHwiClientConfigurationCombinationValues))]
+ public async Task GetVersionTestsAsync(HwiClient client)
+ {
+ using (var cts = new CancellationTokenSource(ReasonableRequestTimeout))
+ {
+ Version version = await client.GetVersionAsync(cts.Token);
+ Assert.Equal(new Version("1.0.1"), version);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(GetHwiClientConfigurationCombinationValues))]
+ public async Task GetHelpTestsAsync(HwiClient client)
+ {
+ using (var cts = new CancellationTokenSource(ReasonableRequestTimeout))
+ {
+ string help = await client.GetHelpAsync(cts.Token);
+ Assert.NotEmpty(help);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(GetHwiClientConfigurationCombinationValues))]
+ public async Task ThrowOperationCanceledExceptionsAsync(HwiClient client)
+ {
+ using (var cts = new CancellationTokenSource())
+ {
+ cts.Cancel();
+ await Assert.ThrowsAsync<OperationCanceledException>(async () => await client.GetVersionAsync(cts.Token));
+ await Assert.ThrowsAsync<OperationCanceledException>(async () => await client.GetHelpAsync(cts.Token));
+ }
+ }
+
+ #endregion Tests
+
+ #region HelperMethods
+
public static IEnumerable<object[]> GetDifferentNetworkValues()
{
var networks = new List<Network>
@@ -33,28 +85,21 @@ namespace WalletWasabi.Tests.HwiTests
}
}
- [Fact]
- public void ConstructorThrowsArgumentNullException()
- {
- Assert.Throws<ArgumentNullException>(() => new HwiClient(null));
- }
-
- [Theory]
- [MemberData(nameof(GetDifferentNetworkValues))]
- public async Task GetVersionTestsAsync(Network network)
+ public static IEnumerable<object[]> GetHwiClientConfigurationCombinationValues()
{
- var client = new HwiClient(network);
-
- using (var cts = new CancellationTokenSource(3000))
+ var networks = new List<Network>
{
- Version version = await client.GetVersionAsync(cts.Token);
- Assert.Equal(new Version("1.0.1"), version);
- }
+ Network.Main,
+ Network.TestNet,
+ Network.RegTest
+ };
- using (var cts = new CancellationTokenSource(1))
+ foreach (Network network in networks)
{
- await Assert.ThrowsAsync<OperationCanceledException>(async () => await client.GetVersionAsync(cts.Token));
+ yield return new object[] { new HwiClient(network) };
}
}
+
+ #endregion HelperMethods
}
}

MODIFY
WalletWasabi/Extensions/ProcessExtensions.cs
WalletWasabi/Extensions/ProcessExtensions.cs
@@ -4,16 +4,55 @@ using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using WalletWasabi.Logging;
namespace System.Diagnostics
{
public static class ProcessExtensions
{
- public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken)
+ private static Dictionary<int, TaskCompletionSource<bool>> AwaitingTaskCompletitionSources { get; } = new Dictionary<int, TaskCompletionSource<bool>>();
+
+ public static async Task WaitForExitAsync(this Process process, CancellationToken cancel)
+ {
+ // https://stackoverflow.com/a/12858633
+ var tcs = new TaskCompletionSource<bool>();
+ try
{
- while (!process.HasExited)
+ AwaitingTaskCompletitionSources.Add(process.Id, tcs);
+ process.EnableRaisingEvents = true;
+ process.Exited += Process_Exited;
+
+ try
+ {
+ await tcs.Task.WithAwaitCancellationAsync(cancel).ConfigureAwait(false);
+ }
+ catch (OperationCanceledException ex)
+ {
+ if (!process.HasExited) // Final check.
+ {
+ // Originally it was TaskCanceledException here, so I don't want to change the behavior.
+ throw new TaskCanceledException("Waiting for process exiting was canceled.", ex, cancel);
+ }
+ }
+ }
+ finally
+ {
+ process.Exited -= Process_Exited;
+ AwaitingTaskCompletitionSources.Remove(process.Id);
+ }
+ }
+
+ private static void Process_Exited(object sender, EventArgs e)
+ {
+ try
+ {
+ var proc = sender as Process;
+ TaskCompletionSource<bool> tcs = AwaitingTaskCompletitionSources[proc.Id];
+ tcs.SetResult(true);
+ }
+ catch (Exception ex)
{
- await Task.Delay(100, cancellationToken).ConfigureAwait(false); // TODO: https://github.com/zkSNACKs/WalletWasabi/issues/1452
+ Logger.LogError(ex, nameof(ProcessExtensions));
}
}
}

MODIFY
WalletWasabi/Hwi2/HwiClient.cs
WalletWasabi/Hwi2/HwiClient.cs
@@ -12,19 +12,28 @@ namespace WalletWasabi.Hwi2
{
public class HwiClient
{
+ #region PropertiesAndMembers
+
public Network Network { get; }
+ #endregion PropertiesAndMembers
+
+ #region ConstructorsAndInitializers
+
public HwiClient(Network network)
{
Network = Guard.NotNull(nameof(network), network);
}
- public async Task<Version> GetVersionAsync(CancellationToken cancel)
+ #endregion ConstructorsAndInitializers
+
+ #region Commands
+
+ public async Task<string> SendCommandAsync(string arguments, CancellationToken cancel)
{
var fullBaseDirectory = Path.GetFullPath(AppContext.BaseDirectory);
var hwiPath = Path.Combine(fullBaseDirectory, "Hwi2", "Binaries", "hwi-win64", "hwi.exe");
- string arguments = "--version";
try
{
using (var process = Process.Start(
@@ -48,6 +57,19 @@ namespace WalletWasabi.Hwi2
string responseString = await process.StandardOutput.ReadToEndAsync().ConfigureAwait(false);
+ return responseString;
+ }
+ }
+ catch (Exception ex) when (ex is OperationCanceledException || ex is TaskCanceledException || ex is TimeoutException)
+ {
+ throw new OperationCanceledException($"'hwi {arguments}' operation is canceled.", ex);
+ }
+ }
+
+ public async Task<Version> GetVersionAsync(CancellationToken cancel)
+ {
+ string responseString = await SendCommandAsync("--version", cancel).ConfigureAwait(false);
+
// Example output: hwi 1.0.0
if (TryParseVersion(responseString, "hwi", out Version v1))
{
@@ -62,13 +84,18 @@ namespace WalletWasabi.Hwi2
throw new FormatException($"Cannot parse version from HWI's response. Response: {responseString}.");
}
- }
- catch (Exception ex) when (ex is OperationCanceledException || ex is TaskCanceledException || ex is TimeoutException)
+
+ public async Task<string> GetHelpAsync(CancellationToken cancel)
{
- throw new OperationCanceledException($"'hwi {arguments}' operation is canceled.", ex);
- }
+ string responseString = await SendCommandAsync("--help", cancel).ConfigureAwait(false);
+
+ return responseString;
}
+ #endregion Commands
+
+ #region Helpers
+
public static bool TryParseVersion(string hwiResponse, string substringFrom, out Version version)
{
int startIndex = hwiResponse.IndexOf(substringFrom) + substringFrom.Length;
@@ -82,5 +109,7 @@ namespace WalletWasabi.Hwi2
return false;
}
+
+ #endregion Helpers
}
}

Implement help command.