Add a test class for `TorProcessManager`.
ADD
WalletWasabi.Tests/UnitTests/Tor/TorProcessManagerTests.cs
+using Moq;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.IO.Pipelines;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using WalletWasabi.Microservices;
+using WalletWasabi.Tor;
+using WalletWasabi.Tor.Control;
+using WalletWasabi.Tor.Socks5;
+using Xunit;
+
+namespace WalletWasabi.Tests.UnitTests.Tor
+{
+ /// <summary>Tests for <see cref="TorProcessManager"/> class.</summary>
+ public class TorProcessManagerTests
+ {
+ private static readonly IPEndPoint DummyTorControlEndpoint = new(IPAddress.Loopback, 7777);
+
+ /// <summary>
+ /// Verifies that in case Tor process is successfully started and Tor Control is set up,
+ /// <see cref="TorProcessManager.StartAsync(CancellationToken)"/> returns <c>true</c>.
+ /// </summary>
+ [Fact]
+ public async Task StartProcessAsync()
+ {
+ using CancellationTokenSource timeoutCts = new(TimeSpan.FromMinutes(2));
+
+ // Tor settings.
+ string dataDir = Path.Combine("temp", "tempDataDir");
+ string distributionFolder = "tempDistributionDir";
+ TorSettings settings = new(dataDir, distributionFolder, terminateOnExit: true, owningProcessId: 7);
+
+ // Dummy Tor process.
+ Mock<ProcessAsync> mockProcess = new(MockBehavior.Strict, new ProcessStartInfo());
+ mockProcess.Setup(p => p.WaitForExitAsync(It.IsAny<CancellationToken>(), It.IsAny<bool>()))
+ .Returns(async (CancellationToken cancellationToken, bool killOnCancel) => await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken).ConfigureAwait(false));
+
+ // Set up Tor control client.
+ Pipe toServer = new();
+ Pipe toClient = new();
+ await using TorControlClient controlClient = new(pipeReader: toClient.Reader, pipeWriter: toServer.Writer);
+
+ // Set up Tor process manager.
+ Mock<TorTcpConnectionFactory> mockTcpConnectionFactory = new(MockBehavior.Strict, DummyTorControlEndpoint);
+ mockTcpConnectionFactory.Setup(c => c.IsTorRunningAsync())
+ .ReturnsAsync(false);
+
+ Mock<TorProcessManager> mockTorProcessManager = new(MockBehavior.Strict, settings, mockTcpConnectionFactory.Object) { CallBase = true };
+ mockTorProcessManager.Setup(c => c.StartProcess(It.IsAny<string>()))
+ .Returns(mockProcess.Object);
+ mockTorProcessManager.Setup(c => c.EnsureRunningAsync(It.IsAny<ProcessAsync>(), It.IsAny<CancellationToken>()))
+ .ReturnsAsync(true);
+ mockTorProcessManager.Setup(c => c.InitTorControlAsync(It.IsAny<CancellationToken>()))
+ .ReturnsAsync(controlClient);
+
+ TorProcessManager manager = mockTorProcessManager.Object;
+ bool result = await manager.StartAsync(timeoutCts.Token);
+ Assert.True(result);
+ }
+ }
+}

MODIFY
WalletWasabi/Microservices/ProcessAsync.cs
WalletWasabi/Microservices/ProcessAsync.cs
@@ -22,7 +22,7 @@ namespace WalletWasabi.Microservices
{
}
- private ProcessAsync(Process process)
+ internal ProcessAsync(Process process)
{
Process = process;
}
@@ -80,7 +80,7 @@ namespace WalletWasabi.Microservices
/// <param name="cancellationToken">Cancellation token.</param>
/// <param name="killOnCancel">If <c>true</c> the process will be killed (with entire process tree) when this asynchronous action is canceled via <paramref name="cancellationToken"/> token.</param>
/// <returns><see cref="Task"/>.</returns>
- public async Task WaitForExitAsync(CancellationToken cancellationToken, bool killOnCancel = false)
+ public virtual async Task WaitForExitAsync(CancellationToken cancellationToken, bool killOnCancel = false)
{
if (Process.HasExited)
{

MODIFY
WalletWasabi/Tor/Socks5/TorTcpConnectionFactory.cs
WalletWasabi/Tor/Socks5/TorTcpConnectionFactory.cs
@@ -5,7 +5,6 @@ using System.Net.Security;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
-using WalletWasabi.Crypto.Randomness;
using WalletWasabi.Helpers;
using WalletWasabi.Logging;
using WalletWasabi.Tor.Socks5.Exceptions;
@@ -120,7 +119,7 @@ namespace WalletWasabi.Tor.Socks5
/// <summary>
/// Checks whether communication can be established with Tor over <see cref="TorSocks5EndPoint"/> endpoint.
/// </summary>
- public async Task<bool> IsTorRunningAsync()
+ public async virtual Task<bool> IsTorRunningAsync()
{
try
{

MODIFY
WalletWasabi/Tor/TorProcessManager.cs
WalletWasabi/Tor/TorProcessManager.cs
@@ -19,12 +19,18 @@ namespace WalletWasabi.Tor
{
private bool _disposed = false;
- public TorProcessManager(TorSettings settings)
+ public TorProcessManager(TorSettings settings) :
+ this(settings, new(settings.SocksEndpoint))
+ {
+ }
+
+ /// <summary>For tests.</summary>
+ internal TorProcessManager(TorSettings settings, TorTcpConnectionFactory tcpConnectionFactory)
{
TorProcess = null;
TorControlClient = null;
Settings = settings;
- TcpConnectionFactory = new(settings.SocksEndpoint);
+ TcpConnectionFactory = tcpConnectionFactory;
}
private ProcessAsync? TorProcess { get; set; }
@@ -98,7 +104,7 @@ namespace WalletWasabi.Tor
}
/// <summary>Ensure <paramref name="process"/> is actually running.</summary>
- private async Task<bool> EnsureRunningAsync(ProcessAsync process, CancellationToken token)
+ internal virtual async Task<bool> EnsureRunningAsync(ProcessAsync process, CancellationToken token)
{
int i = 0;
while (true)
@@ -132,7 +138,7 @@ namespace WalletWasabi.Tor
}
/// <param name="arguments">Command line arguments to start Tor OS process with.</param>
- private ProcessAsync StartProcess(string arguments)
+ internal virtual ProcessAsync StartProcess(string arguments)
{
ProcessStartInfo startInfo = new()
{
@@ -165,7 +171,7 @@ namespace WalletWasabi.Tor
/// <summary>Connects to Tor control using a TCP client or throws <see cref="TorControlException"/>.</summary>
/// <exception cref="TorControlException">When authentication fails for some reason.</exception>
/// <seealso href="https://gitweb.torproject.org/torspec.git/tree/control-spec.txt">This method follows instructions in 3.23. TAKEOWNERSHIP.</seealso>
- private async Task<TorControlClient> InitTorControlAsync(CancellationToken token = default)
+ internal virtual async Task<TorControlClient> InitTorControlAsync(CancellationToken token = default)
{
// Get cookie.
string cookieString = ByteHelpers.ToHex(File.ReadAllBytes(Settings.CookieAuthFilePath));

Add a test class for `TorProcessManager`.