diff --git a/NuGet.Config b/NuGet.Config
index ba6d1755..437e25e1 100644
--- a/NuGet.Config
+++ b/NuGet.Config
@@ -2,5 +2,6 @@
+
\ No newline at end of file
diff --git a/benchmarks/NuGet.Config b/benchmarks/NuGet.Config
deleted file mode 100644
index deec2ce6..00000000
--- a/benchmarks/NuGet.Config
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/CoreIpc.sln b/src/CoreIpc.sln
index d863ba5d..8198f5ab 100644
--- a/src/CoreIpc.sln
+++ b/src/CoreIpc.sln
@@ -8,7 +8,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{676A208A-2F08-4749-A833-F8D2BCB1B147}"
ProjectSection(SolutionItems) = preProject
Directory.Build.targets = Directory.Build.targets
- NuGet.Config = NuGet.Config
+ ..\NuGet.Config = ..\NuGet.Config
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Playground", "Playground\Playground.csproj", "{F0365E40-DA73-4583-A363-89CBEF68A4C6}"
diff --git a/src/UiPath.CoreIpc/Client/ServiceClient.cs b/src/UiPath.CoreIpc/Client/ServiceClient.cs
index e546e4fd..12ded021 100644
--- a/src/UiPath.CoreIpc/Client/ServiceClient.cs
+++ b/src/UiPath.CoreIpc/Client/ServiceClient.cs
@@ -2,26 +2,8 @@
internal abstract class ServiceClient : IDisposable
{
- #region " NonGeneric-Generic adapter cache "
- private static readonly MethodInfo GenericDefinition = ((Func>)Invoke).Method.GetGenericMethodDefinition();
- private static readonly ConcurrentDictionary ReturnTypeToInvokeDelegate = new();
- private static InvokeDelegate GetInvokeDelegate(Type returnType) => ReturnTypeToInvokeDelegate.GetOrAdd(returnType, CreateInvokeDelegate);
- private static InvokeDelegate CreateInvokeDelegate(Type returnType)
- => GenericDefinition.MakeGenericDelegate(
- returnType.IsGenericType
- ? returnType.GetGenericArguments()[0]
- : typeof(object));
-
- private static Task Invoke(ServiceClient serviceClient, MethodInfo method, object?[] args) => serviceClient.Invoke(method, args);
- #endregion
-
- protected abstract TimeSpan RequestTimeout { get; }
- protected abstract BeforeCallHandler? BeforeCall { get; }
- protected abstract ILogger? Log { get; }
- protected abstract string DebugName { get; }
- protected abstract ISerializer? Serializer { get; }
+ protected abstract IServiceClientConfig Config { get; }
public abstract Stream? Network { get; }
-
public event EventHandler? ConnectionClosed;
private readonly Type _interfaceType;
@@ -33,7 +15,6 @@ protected ServiceClient(Type interfaceType)
}
protected void RaiseConnectionClosed() => ConnectionClosed?.Invoke(this, EventArgs.Empty);
-
public virtual ValueTask CloseConnection() => throw new NotSupportedException();
public object? Invoke(MethodInfo method, object?[] args) => GetInvokeDelegate(method.ReturnType)(this, method, args);
@@ -64,7 +45,7 @@ async Task Invoke()
{
CancellationToken cancellationToken = default;
TimeSpan messageTimeout = default;
- TimeSpan clientTimeout = RequestTimeout;
+ TimeSpan clientTimeout = Config.RequestTimeout;
Stream? uploadStream = null;
var methodName = method.Name;
@@ -77,10 +58,10 @@ async Task Invoke()
var (connection, newConnection) = await EnsureConnection(ct);
- if (BeforeCall is not null)
+ if (Config.BeforeCall is not null)
{
var callInfo = new CallInfo(newConnection, method, args);
- await BeforeCall(callInfo, ct);
+ await Config.BeforeCall(callInfo, ct);
}
var requestId = connection.NewRequestId();
@@ -89,11 +70,11 @@ async Task Invoke()
UploadStream = uploadStream
};
- Log?.ServiceClientCalling(methodName, requestId, DebugName);
+ Config.Logger?.ServiceClientCalling(methodName, requestId, Config.DebugName);
var response = await connection.RemoteCall(request, ct); // returns user errors instead of throwing them (could throw for system bugs)
- Log?.ServiceClientCalled(methodName, requestId, DebugName);
+ Config.Logger?.ServiceClientCalled(methodName, requestId, Config.DebugName);
- return response.Deserialize(Serializer);
+ return response.Deserialize(Config.Serializer);
}
catch (Exception ex)
{
@@ -127,7 +108,7 @@ string[] SerializeArguments()
break;
}
- result[index] = Serializer.OrDefault().Serialize(args[index]);
+ result[index] = Config.Serializer.OrDefault().Serialize(args[index]);
}
return result;
@@ -142,9 +123,22 @@ public void Dispose()
}
private void Dispose(bool disposing)
{
- Log?.ServiceClientDispose(DebugName);
+ Config.Logger?.ServiceClientDispose(Config.DebugName);
}
- public override string ToString() => DebugName;
+ public override string ToString() => Config.DebugName;
+
+ #region Generic adapter cache
+ private static readonly MethodInfo GenericDefinition = ((Func>)Invoke).Method.GetGenericMethodDefinition();
+ private static readonly ConcurrentDictionary ReturnTypeToInvokeDelegate = new();
+ private static InvokeDelegate GetInvokeDelegate(Type returnType) => ReturnTypeToInvokeDelegate.GetOrAdd(returnType, CreateInvokeDelegate);
+ private static InvokeDelegate CreateInvokeDelegate(Type returnType)
+ => GenericDefinition.MakeGenericDelegate(
+ returnType.IsGenericType
+ ? returnType.GetGenericArguments()[0]
+ : typeof(object));
+
+ private static Task Invoke(ServiceClient serviceClient, MethodInfo method, object?[] args) => serviceClient.Invoke(method, args);
+ #endregion
}
internal sealed class ServiceClientProper : ServiceClient
@@ -208,10 +202,16 @@ public override async ValueTask CloseConnection()
return (LatestConnection, newlyConnected: false);
}
- LatestConnection = new Connection(await Connect(ct), Serializer, Log, DebugName);
+ if (Config.BeforeConnect is not null)
+ {
+ await Config.BeforeConnect(ct);
+ }
+
+ var network = await Connect(ct);
+ LatestConnection = new Connection(network, Config.Serializer, Config.Logger, Config.DebugName);
var router = new Router(_client.Config.CreateCallbackRouterConfig(), _client.Config.ServiceProvider);
_latestServer = new Server(router, _client.Config.RequestTimeout, LatestConnection);
- LatestConnection.Listen().LogException(Log, DebugName);
+ LatestConnection.Listen().LogException(Config.Logger, Config.DebugName);
return (LatestConnection, newlyConnected: true);
}
}
@@ -228,11 +228,7 @@ private async Task Connect(CancellationToken ct)
return network;
}
- protected override TimeSpan RequestTimeout => _client.Config.RequestTimeout;
- protected override BeforeCallHandler? BeforeCall => _client.Config.BeforeCall;
- protected override ILogger? Log => _client.Config.Logger;
- protected override string DebugName => _client.Transport.ToString();
- protected override ISerializer? Serializer => _client.Config.Serializer;
+ protected override IServiceClientConfig Config => _client.Config;
}
internal sealed class ServiceClientForCallback : ServiceClient
@@ -251,9 +247,5 @@ public ServiceClientForCallback(Connection connection, Listener listener, Type i
protected override Task<(Connection connection, bool newlyConnected)> EnsureConnection(CancellationToken ct)
=> Task.FromResult((_connection, newlyConnected: false));
- protected override TimeSpan RequestTimeout => _listener.Config.RequestTimeout;
- protected override BeforeCallHandler? BeforeCall => null;
- protected override ILogger? Log => null;
- protected override string DebugName => $"ReverseClient for {_listener}";
- protected override ISerializer? Serializer => null;
+ protected override IServiceClientConfig Config => _listener.Config;
}
diff --git a/src/UiPath.CoreIpc/Config/ClientConfig.cs b/src/UiPath.CoreIpc/Config/ClientConfig.cs
index f2aed93a..0187092f 100644
--- a/src/UiPath.CoreIpc/Config/ClientConfig.cs
+++ b/src/UiPath.CoreIpc/Config/ClientConfig.cs
@@ -1,15 +1,21 @@
-namespace UiPath.Ipc;
+using System.ComponentModel;
-public sealed record ClientConfig : EndpointConfig
+namespace UiPath.Ipc;
+
+public sealed record ClientConfig : EndpointConfig, IServiceClientConfig
{
public EndpointCollection? Callbacks { get; init; }
public IServiceProvider? ServiceProvider { get; init; }
public ILogger? Logger { get; init; }
+ public BeforeConnectHandler? BeforeConnect { get; init; }
public BeforeCallHandler? BeforeCall { get; init; }
public TaskScheduler? Scheduler { get; init; }
public ISerializer? Serializer { get; set; }
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string DebugName { get; set; } = null!;
+
internal void Validate()
{
var haveDeferredInjectedCallbacks = Callbacks?.Any(x => x.Service.MaybeGetServiceProvider() is null && x.Service.MaybeGetInstance() is null) ?? false;
diff --git a/src/UiPath.CoreIpc/Config/IServiceClientConfig.cs b/src/UiPath.CoreIpc/Config/IServiceClientConfig.cs
new file mode 100644
index 00000000..95d3a29e
--- /dev/null
+++ b/src/UiPath.CoreIpc/Config/IServiceClientConfig.cs
@@ -0,0 +1,11 @@
+namespace UiPath.Ipc;
+
+internal interface IServiceClientConfig
+{
+ TimeSpan RequestTimeout { get; }
+ BeforeConnectHandler? BeforeConnect { get; }
+ BeforeCallHandler? BeforeCall { get; }
+ ILogger? Logger { get; }
+ ISerializer? Serializer { get; }
+ string DebugName { get; }
+}
diff --git a/src/UiPath.CoreIpc/Config/IpcClient.cs b/src/UiPath.CoreIpc/Config/IpcClient.cs
index 28ff2f37..0c1735ef 100644
--- a/src/UiPath.CoreIpc/Config/IpcClient.cs
+++ b/src/UiPath.CoreIpc/Config/IpcClient.cs
@@ -28,5 +28,7 @@ internal void Validate()
Config.Validate();
Transport.Validate();
+
+ Config.DebugName ??= Transport.ToString();
}
}
diff --git a/src/UiPath.CoreIpc/Config/ListenerConfig.cs b/src/UiPath.CoreIpc/Config/ListenerConfig.cs
index cd221a62..2c41dcc6 100644
--- a/src/UiPath.CoreIpc/Config/ListenerConfig.cs
+++ b/src/UiPath.CoreIpc/Config/ListenerConfig.cs
@@ -2,15 +2,13 @@
namespace UiPath.Ipc;
-public abstract record ListenerConfig : EndpointConfig
+public abstract record ListenerConfig : EndpointConfig, IServiceClientConfig
{
public int ConcurrentAccepts { get; init; } = 5;
public byte MaxReceivedMessageSizeInMegabytes { get; init; } = 2;
public X509Certificate? Certificate { get; init; }
-
internal int MaxMessageSize => MaxReceivedMessageSizeInMegabytes * 1024 * 1024;
- internal string DebugName => GetType().Name;
internal IEnumerable Validate() => Enumerable.Empty();
internal override RouterConfig CreateRouterConfig(IpcServer server)
@@ -20,4 +18,14 @@ internal override RouterConfig CreateRouterConfig(IpcServer server)
{
Scheduler = endpoint.Scheduler ?? server.Scheduler
});
+
+ #region IServiceClientConfig
+ /// Do not implement explicitly, as it must be implicitly implemented by .
+
+ BeforeConnectHandler? IServiceClientConfig.BeforeConnect => null;
+ BeforeCallHandler? IServiceClientConfig.BeforeCall => null;
+ ILogger? IServiceClientConfig.Logger => null;
+ ISerializer? IServiceClientConfig.Serializer => null!;
+ string IServiceClientConfig.DebugName => $"CallbackClient for {this}";
+ #endregion
}
diff --git a/src/UiPath.CoreIpc/GlobalUsings.cs b/src/UiPath.CoreIpc/GlobalUsings.cs
index c3b8f439..66625222 100644
--- a/src/UiPath.CoreIpc/GlobalUsings.cs
+++ b/src/UiPath.CoreIpc/GlobalUsings.cs
@@ -1,4 +1,5 @@
global using UiPath.Ipc.Extensibility;
+global using BeforeConnectHandler = System.Func;
global using BeforeCallHandler = System.Func;
global using InvokeDelegate = System.Func;
global using Accept = System.Func>;
diff --git a/src/UiPath.CoreIpc/Server/Listener.cs b/src/UiPath.CoreIpc/Server/Listener.cs
index 356c3056..37b9a532 100644
--- a/src/UiPath.CoreIpc/Server/Listener.cs
+++ b/src/UiPath.CoreIpc/Server/Listener.cs
@@ -82,7 +82,7 @@ protected Listener(IpcServer server, ListenerConfig config)
{
Config = config;
Server = server;
- Logger = server.ServiceProvider.GetRequiredService().CreateLogger(config.DebugName);
+ Logger = server.ServiceProvider.GetRequiredService().CreateLogger(categoryName: config.ToString());
_disposeTask = new(DisposeCore);
}
@@ -131,7 +131,7 @@ public void LogError(Exception exception, string message)
protected override async Task DisposeCore()
{
- Log($"Stopping listener {Config.DebugName}...");
+ Log($"Stopping listener {Config}...");
_cts.Cancel();
try
{
@@ -139,11 +139,11 @@ protected override async Task DisposeCore()
}
catch (OperationCanceledException ex) when (ex.CancellationToken == _cts.Token)
{
- Log($"Stopping listener {Config.DebugName} threw OCE.");
+ Log($"Stopping listener {Config} threw OCE.");
}
catch (Exception ex)
{
- LogError(ex, $"Stopping listener {Config.DebugName} failed.");
+ LogError(ex, $"Stopping listener {Config} failed.");
}
await State.DisposeAsync();
_cts.Dispose();
@@ -151,7 +151,7 @@ protected override async Task DisposeCore()
private async Task Listen(CancellationToken ct)
{
- Log($"Starting listener {Config.DebugName}...");
+ Log($"Starting listener {Config}...");
await Task.WhenAll(Enumerable.Range(1, Config.ConcurrentAccepts).Select(async _ =>
{
@@ -167,17 +167,15 @@ private async Task AcceptConnection(CancellationToken ct)
try
{
var network = await serverConnection.AcceptClient(ct);
- serverConnection.Listen(network, ct).LogException(Logger, Config.DebugName);
+ serverConnection.Listen(network, ct).LogException(Logger, Config);
}
catch (Exception ex)
{
serverConnection.Dispose();
if (!ct.IsCancellationRequested)
{
- Logger.LogException(ex, Config.DebugName);
+ Logger.LogException(ex, Config);
}
}
}
-
- public override string ToString() => Config.ToString();
}
diff --git a/src/UiPath.CoreIpc/Server/ServerConnection.cs b/src/UiPath.CoreIpc/Server/ServerConnection.cs
index 20be603b..bc4de3e8 100644
--- a/src/UiPath.CoreIpc/Server/ServerConnection.cs
+++ b/src/UiPath.CoreIpc/Server/ServerConnection.cs
@@ -69,7 +69,7 @@ TCallbackInterface IClient.GetCallback() where TCallbackInte
TCallbackInterface CreateCallback(Type callbackContract)
{
- Listener.Logger.LogInformation($"Create callback {callbackContract} {Listener.Config.DebugName}");
+ Listener.Logger.LogInformation($"Create callback {callbackContract} {Listener.Config}");
_connectionAsTask ??= Task.FromResult(Connection!);
diff --git a/src/UiPath.Ipc.Tests/ComputingTests.cs b/src/UiPath.Ipc.Tests/ComputingTests.cs
index 1fea8e13..49a79ec8 100644
--- a/src/UiPath.Ipc.Tests/ComputingTests.cs
+++ b/src/UiPath.Ipc.Tests/ComputingTests.cs
@@ -1,4 +1,12 @@
-using System.Collections.Concurrent;
+using Newtonsoft.Json;
+using Nito.Disposables;
+using System.Collections.Concurrent;
+using System.Runtime.InteropServices;
+using System.Text;
+using UiPath.Ipc.Transport.NamedPipe;
+using UiPath.Ipc.Transport.Tcp;
+using UiPath.Ipc.Transport.WebSocket;
+using Xunit;
using Xunit.Abstractions;
namespace UiPath.Ipc.Tests;
@@ -9,12 +17,12 @@ public abstract class ComputingTests : TestBase
protected readonly ComputingCallback _computingCallback = new();
private readonly Lazy _service;
- private readonly Lazy _proxy;
+ private readonly Lazy _proxy;
protected ComputingService Service => _service.Value;
- protected IComputingService Proxy => _proxy.Value;
+ protected IComputingService Proxy => _proxy.Value!;
- protected sealed override IpcProxy IpcProxy => Proxy as IpcProxy ?? throw new InvalidOperationException($"Proxy was expected to be a {nameof(IpcProxy)} but was not.");
+ protected sealed override IpcProxy? IpcProxy => Proxy as IpcProxy;
protected sealed override Type ContractType => typeof(IComputingService);
protected readonly ConcurrentBag _clientBeforeCalls = new();
@@ -82,7 +90,7 @@ public async Task ClientTimeouts_ShouldWork()
await Proxy.Wait(Timeout.InfiniteTimeSpan).ShouldThrowAsync();
await Proxy.GetCallbackThreadName(
- duration: TimeSpan.Zero,
+ waitOnServer: TimeSpan.Zero,
message: new()
{
RequestTimeout = Timeouts.DefaultRequest
@@ -93,7 +101,7 @@ await Proxy.GetCallbackThreadName(
private sealed class ShortClientTimeout : OverrideConfig
{
- public override IpcClient Override(IpcClient client) => client.WithRequestTimeout(TimeSpan.FromMilliseconds(10));
+ public override IpcClient? Override(Func client) => client().WithRequestTimeout(TimeSpan.FromMilliseconds(10));
}
[Theory, IpcAutoData]
@@ -102,7 +110,7 @@ public async Task CallsWithArraysOfStructsAsParams_ShouldWork(ComplexNumber a, C
[Fact]
public async Task Callbacks_ShouldWork()
- => await Proxy.GetCallbackThreadName(duration: TimeSpan.Zero).ShouldBeAsync(Names.GuiThreadName);
+ => await Proxy.GetCallbackThreadName(waitOnServer: TimeSpan.Zero).ShouldBeAsync(Names.GuiThreadName);
[Fact]
public async Task CallbacksWithParams_ShouldWork()
@@ -141,4 +149,139 @@ public async Task ServerBeforeCall_WhenSync_ShouldShareAsyncLocalContextWithTheT
await Proxy.GetCallContext().ShouldBeAsync(expectedCallContext);
}
+
+ [Fact]
+ [OverrideConfig(typeof(SetBeforeConnect))]
+ public async Task BeforeConnect_ShouldWork()
+ {
+ int callCount = 0;
+ SetBeforeConnect.Set(async _ => callCount++);
+
+ await Proxy.AddFloats(1, 2).ShouldBeAsync(3);
+ callCount.ShouldBe(1);
+
+ await Proxy.AddFloats(1, 2).ShouldBeAsync(3);
+ callCount.ShouldBe(1);
+
+ await IpcProxy.CloseConnection();
+ await Proxy.AddFloats(1, 2).ShouldBeAsync(3);
+ callCount.ShouldBe(2);
+ }
+
+ private sealed class SetBeforeConnect : OverrideConfig
+ {
+ private static readonly AsyncLocal ValueStorage = new();
+ public static void Set(BeforeConnectHandler value) => ValueStorage.Value = value;
+
+ public override IpcClient? Override(Func client)
+ => client().WithBeforeConnect(ct => ValueStorage.Value.ShouldNotBeNull().Invoke(ct));
+ }
+
+#if !NET461
+ [SkippableFact]
+#endif
+ [OverrideConfig(typeof(DisableInProcClientServer))]
+ public async Task BeforeConnect_ShouldStartExternalServerJIT()
+ {
+ Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), "Test works only on Windows.");
+
+ using var whereDotNet = new Process
+ {
+ StartInfo =
+ {
+ FileName = "where.exe",
+ Arguments = "dotnet.exe",
+ }
+ };
+ var pathDotNet = await whereDotNet.RunReturnStdOut();
+
+ var externalServerParams = RandomServerParams();
+ var arg = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(externalServerParams)));
+
+ var pipeName = $"{Guid.NewGuid():N}";
+
+ using var serverProcess = new Process
+ {
+ StartInfo =
+ {
+ FileName = pathDotNet,
+ Arguments = $"\"{Assembly.GetExecutingAssembly().Location}\" {arg}",
+ UseShellExecute = false,
+ },
+ };
+ using var killProcess = new Disposable(() =>
+ {
+ try
+ {
+ serverProcess.Kill();
+ }
+ catch
+ {
+ }
+ _outputHelper.WriteLine("Killed server process");
+ });
+ var proxy = new IpcClient
+ {
+ Config = new()
+ {
+ Scheduler = GuiScheduler,
+ BeforeConnect = async (_) =>
+ {
+ serverProcess.Start();
+ var time = TimeSpan.FromSeconds(1);
+ _outputHelper.WriteLine($"Server started. Waiting {time}. PID={serverProcess.Id}");
+ await Task.Delay(time);
+ },
+ },
+ Transport = externalServerParams.CreateClientTransport()
+ }.GetProxy();
+
+ await proxy.AddFloats(1, 2).ShouldBeAsync(3);
+ }
+
+ public abstract IAsyncDisposable? RandomTransportPair(out ListenerConfig listener, out ClientTransport transport);
+
+ public abstract ExternalServerParams RandomServerParams();
+ public readonly record struct ExternalServerParams(ServerKind Kind, string? PipeName = null, int Port = 0)
+ {
+ public IAsyncDisposable? CreateListenerConfig(out ListenerConfig listenerConfig)
+ {
+ switch (Kind)
+ {
+ case ServerKind.NamedPipes:
+ {
+ listenerConfig = new NamedPipeListener() { PipeName = PipeName! };
+ return null;
+ }
+ case ServerKind.Tcp:
+ {
+ listenerConfig = new TcpListener() { EndPoint = new(System.Net.IPAddress.Loopback, Port) };
+ return null;
+ }
+ case ServerKind.WebSockets:
+ {
+ var context = new WebSocketContext(Port);
+ listenerConfig = new WebSocketListener { Accept = context.Accept };
+ return context;
+ }
+ default:
+ throw new NotSupportedException($"Kind not supported. Kind was {Kind}");
+ }
+ }
+
+ public ClientTransport CreateClientTransport() => Kind switch
+ {
+ ServerKind.NamedPipes => new NamedPipeTransport() { PipeName = PipeName! },
+ ServerKind.Tcp => new TcpTransport() { EndPoint = new(System.Net.IPAddress.Loopback, Port) },
+ ServerKind.WebSockets => new WebSocketTransport() { Uri = new($"ws://localhost:{Port}") },
+ _ => throw new NotSupportedException($"Kind not supported. Kind was {Kind}")
+ };
+ }
+ public enum ServerKind { NamedPipes, Tcp, WebSockets }
+
+ private sealed class DisableInProcClientServer : OverrideConfig
+ {
+ public override ListenerConfig? Override(Func listener) => null;
+ public override IpcClient? Override(Func client) => null;
+ }
}
diff --git a/src/UiPath.Ipc.Tests/ComputingTestsOverNamedPipes.cs b/src/UiPath.Ipc.Tests/ComputingTestsOverNamedPipes.cs
index 8811da92..6378a339 100644
--- a/src/UiPath.Ipc.Tests/ComputingTestsOverNamedPipes.cs
+++ b/src/UiPath.Ipc.Tests/ComputingTestsOverNamedPipes.cs
@@ -17,5 +17,16 @@ public ComputingTestsOverNamedPipes(ITestOutputHelper outputHelper) : base(outpu
{
PipeName = PipeName,
AllowImpersonation = true,
- };
+ };
+
+ public override IAsyncDisposable? RandomTransportPair(out ListenerConfig listener, out ClientTransport transport)
+ {
+ var pipeName = $"{Guid.NewGuid():N}";
+ listener = new NamedPipeListener() { PipeName = pipeName };
+ transport = new NamedPipeTransport() { PipeName = pipeName };
+ return null;
+ }
+
+ public override ExternalServerParams RandomServerParams()
+ => new(ServerKind.NamedPipes, PipeName: $"{Guid.NewGuid():N}");
}
diff --git a/src/UiPath.Ipc.Tests/ComputingTestsOverTcp.cs b/src/UiPath.Ipc.Tests/ComputingTestsOverTcp.cs
index d6fdf0ae..3441fb09 100644
--- a/src/UiPath.Ipc.Tests/ComputingTestsOverTcp.cs
+++ b/src/UiPath.Ipc.Tests/ComputingTestsOverTcp.cs
@@ -18,4 +18,18 @@ protected override ListenerConfig CreateListener()
protected override ClientTransport CreateClientTransport()
=> new TcpTransport() { EndPoint = _endPoint };
+
+ public override IAsyncDisposable? RandomTransportPair(out ListenerConfig listener, out ClientTransport transport)
+ {
+ var endPoint = NetworkHelper.FindFreeLocalPort();
+ listener = new TcpListener() { EndPoint = endPoint };
+ transport = new TcpTransport() { EndPoint = endPoint };
+ return null;
+ }
+
+ public override ExternalServerParams RandomServerParams()
+ {
+ var endPoint = NetworkHelper.FindFreeLocalPort();
+ return new(ServerKind.Tcp, Port: endPoint.Port);
+ }
}
diff --git a/src/UiPath.Ipc.Tests/ComputingTestsOverWebSockets.cs b/src/UiPath.Ipc.Tests/ComputingTestsOverWebSockets.cs
index fd571df5..c342db9d 100644
--- a/src/UiPath.Ipc.Tests/ComputingTestsOverWebSockets.cs
+++ b/src/UiPath.Ipc.Tests/ComputingTestsOverWebSockets.cs
@@ -21,4 +21,18 @@ protected override async Task DisposeAsync()
};
protected override ClientTransport CreateClientTransport()
=> new WebSocketTransport() { Uri = _webSocketContext.ClientUri };
+
+ public override IAsyncDisposable? RandomTransportPair(out ListenerConfig listener, out ClientTransport transport)
+ {
+ var context = new WebSocketContext();
+ listener = new WebSocketListener() { Accept = context.Accept };
+ transport = new WebSocketTransport() { Uri = context.ClientUri };
+ return context;
+ }
+
+ public override ExternalServerParams RandomServerParams()
+ {
+ var endPoint = NetworkHelper.FindFreeLocalPort();
+ return new(ServerKind.WebSockets, Port: endPoint.Port);
+ }
}
\ No newline at end of file
diff --git a/src/UiPath.Ipc.Tests/Config/OverrideConfigAttribute.cs b/src/UiPath.Ipc.Tests/Config/OverrideConfigAttribute.cs
index 102278c8..9435de29 100644
--- a/src/UiPath.Ipc.Tests/Config/OverrideConfigAttribute.cs
+++ b/src/UiPath.Ipc.Tests/Config/OverrideConfigAttribute.cs
@@ -34,6 +34,6 @@ public OverrideConfigAttribute(Type overrideConfigType)
public abstract class OverrideConfig
{
- public virtual ListenerConfig Override(ListenerConfig listener) => listener;
- public virtual IpcClient Override(IpcClient client) => client;
+ public virtual ListenerConfig? Override(Func listener) => listener();
+ public virtual IpcClient? Override(Func client) => client();
}
\ No newline at end of file
diff --git a/src/UiPath.Ipc.Tests/GlobalUsings.cs b/src/UiPath.Ipc.Tests/GlobalUsings.cs
index d4b046bf..1f26ad7a 100644
--- a/src/UiPath.Ipc.Tests/GlobalUsings.cs
+++ b/src/UiPath.Ipc.Tests/GlobalUsings.cs
@@ -1 +1,2 @@
global using Accept = System.Func>;
+global using BeforeConnectHandler = System.Func;
diff --git a/src/UiPath.Ipc.Tests/Helpers/IpcHelpers.cs b/src/UiPath.Ipc.Tests/Helpers/IpcHelpers.cs
index d3263494..399acc8a 100644
--- a/src/UiPath.Ipc.Tests/Helpers/IpcHelpers.cs
+++ b/src/UiPath.Ipc.Tests/Helpers/IpcHelpers.cs
@@ -48,4 +48,11 @@ public static IpcClient WithCallbacks(this IpcClient ipcClient, EndpointCollecti
Config = ipcClient.Config with { Callbacks = callbacks },
Transport = ipcClient.Transport,
};
+
+ public static IpcClient WithBeforeConnect(this IpcClient ipcClient, BeforeConnectHandler beforeConnect)
+ => new()
+ {
+ Config = ipcClient.Config with { BeforeConnect = beforeConnect },
+ Transport = ipcClient.Transport,
+ };
}
\ No newline at end of file
diff --git a/src/UiPath.Ipc.Tests/Helpers/TestRunId.cs b/src/UiPath.Ipc.Tests/Helpers/TestRunId.cs
index d8a43d65..f76d43c8 100644
--- a/src/UiPath.Ipc.Tests/Helpers/TestRunId.cs
+++ b/src/UiPath.Ipc.Tests/Helpers/TestRunId.cs
@@ -4,3 +4,29 @@ public readonly record struct TestRunId(Guid Value)
{
public static TestRunId New() => new(Guid.NewGuid());
}
+
+public static class ProcessHelper
+{
+ public static async Task RunReturnStdOut(this Process process)
+ {
+ process.StartInfo.UseShellExecute = false;
+ process.StartInfo.RedirectStandardOutput = true;
+ process.StartInfo.RedirectStandardError = true;
+
+ TaskCompletionSource