Skip to content

Commit 94ab9d6

Browse files
Add new features, tests, and refactor exceptions
- Refactored `RemoteException` in `Dtos.cs` to handle stack traces differently for .NET Core and other frameworks. - Implemented the `DivideByZero` operation in `SystemService` to test exceptions. - Added `RemoteExceptionStackTrace_ShouldAlsoIncludeClientFrames` test to validate stack trace inclusion of client and server frames. - Added `ShouldPartiallyContainInOrder` method in `ShouldlyHelpers.cs` for enumerable validation with logging. - Removed unused namespaces and improved exception handling for clarity.
1 parent 61952e8 commit 94ab9d6

File tree

5 files changed

+97
-7
lines changed

5 files changed

+97
-7
lines changed

src/UiPath.CoreIpc.Tests/Helpers/ShouldlyHelpers.cs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System.Runtime.CompilerServices;
2+
using System.Text;
3+
using System.Threading.Tasks;
24

35
namespace UiPath.Ipc.Tests;
46

@@ -75,7 +77,7 @@ public static async Task ShouldStallForAtLeastAsync<T>(this Task<T> task, TimeSp
7577
throw new ShouldAssertException($"The task {taskExpression} should stall for at least {lease} but it completed faster.");
7678
}
7779
catch (OperationCanceledException ex) when (ex.CancellationToken == cts.Token)
78-
{
80+
{
7981
}
8082
}
8183

@@ -114,4 +116,45 @@ private static async Task<T> Return<T>(this Task task, T value = default!)
114116
await task;
115117
return value;
116118
}
119+
120+
public static void ShouldPartiallyContainInOrder(this IEnumerable<string> haystack, IEnumerable<string> needles, [CallerArgumentExpression(nameof(haystack))] string? supersetExpression = null, [CallerArgumentExpression(nameof(needles))] string? subsetExpression = null)
121+
{
122+
StringBuilder log = new();
123+
124+
if (!Is())
125+
{
126+
throw new ShouldAssertException($"The enumerable `{supersetExpression}`\r\n\tshould be an ordered superset of the \r\n{subsetExpression}\r\n\tsubset.\r\n{log}");
127+
}
128+
129+
bool Is()
130+
{
131+
using var super = haystack.GetEnumerator();
132+
using var sub = needles.GetEnumerator();
133+
134+
int superIndex = -1;
135+
int subIndex = -1;
136+
137+
while (sub.MoveNext())
138+
{
139+
subIndex++;
140+
141+
advanceSuper:
142+
if (!super.MoveNext())
143+
{
144+
log.AppendLine($"ERROR: token[{subIndex}]=\"{sub.Current}\" not found");
145+
return false;
146+
}
147+
superIndex++;
148+
149+
if (!super.Current.Contains(sub.Current))
150+
{
151+
goto advanceSuper;
152+
}
153+
154+
log.AppendLine($"INFO: token[{subIndex}]=\"{sub.Current}\" found at haystack[{superIndex}](=\"{super.Current}\")");
155+
}
156+
157+
return true;
158+
}
159+
}
117160
}

src/UiPath.CoreIpc.Tests/Services/ISystemService.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ public interface ISystemService
4747
Task<string> DanishNameOfDay(DayOfWeek day, CancellationToken ct);
4848

4949
Task<byte[]> ReverseBytes(byte[] bytes, CancellationToken ct = default);
50+
51+
Task<bool> DivideByZero();
5052
}
5153

5254
public interface IUnregisteredCallback

src/UiPath.CoreIpc.Tests/Services/SystemService.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
using Castle.Core;
2-
using System.Buffers;
1+
using System.Buffers;
32
using System.Globalization;
3+
using System.Runtime.CompilerServices;
44
using System.Text;
55

66
namespace UiPath.Ipc.Tests;
@@ -102,4 +102,15 @@ public Task<byte[]> ReverseBytes(byte[] bytes, CancellationToken ct = default)
102102
}
103103
return Task.FromResult(bytes);
104104
}
105+
106+
public async Task<bool> DivideByZero()
107+
{
108+
ServerFrame1();
109+
return true;
110+
}
111+
112+
[MethodImpl(MethodImplOptions.NoInlining)]
113+
public static void ServerFrame1() => ServerFrame2();
114+
[MethodImpl(MethodImplOptions.NoInlining)]
115+
public static void ServerFrame2() => throw new DivideByZeroException();
105116
}

src/UiPath.CoreIpc.Tests/SystemTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using AutoFixture;
22
using AutoFixture.Xunit2;
33
using Microsoft.Extensions.Hosting;
4+
using System.Runtime.CompilerServices;
45
using System.Text;
6+
using System.Text.RegularExpressions;
57
using System.Threading.Channels;
68
using Xunit.Abstractions;
79

@@ -337,6 +339,28 @@ public async Task IpcServerDispose_ShouldBeIdempotent(Guid guid)
337339
await infiniteTask.ShouldThrowAsync<IOException>().ShouldCompleteInAsync(Timeouts.IpcRoundtrip);
338340
}
339341

342+
[Fact]
343+
public async Task RemoteExceptionStackTrace_ShouldAlsoIncludeClientFrames()
344+
{
345+
var act = ClientFrame1;
346+
347+
(await act.ShouldThrowAsync<RemoteException>())
348+
.StackTrace.ShouldNotBeNull()
349+
.Split('\n')
350+
.ShouldPartiallyContainInOrder([
351+
nameof(SystemService.ServerFrame2),
352+
nameof(SystemService.ServerFrame1),
353+
nameof(ClientFrame2),
354+
nameof(ClientFrame1)]);
355+
356+
357+
[MethodImpl(MethodImplOptions.NoInlining)]
358+
async Task ClientFrame1() => await ClientFrame2();
359+
360+
[MethodImpl(MethodImplOptions.NoInlining)]
361+
async Task ClientFrame2() => _ = await Proxy.DivideByZero();
362+
}
363+
340364
private sealed class UploadStream : StreamBase
341365
{
342366
private readonly Channel<ReadCall> _readCalls = Channel.CreateUnbounded<ReadCall>();

src/UiPath.CoreIpc/Wire/Dtos.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
using System.Diagnostics.CodeAnalysis;
1+
using Newtonsoft.Json;
2+
using System.Diagnostics.CodeAnalysis;
3+
using System.Runtime.ExceptionServices;
24
using System.Text;
3-
using Newtonsoft.Json;
45

56
namespace UiPath.Ipc;
67

@@ -66,13 +67,22 @@ public record Error(string Message, string StackTrace, string Type, Error? Inner
6667

6768
public class RemoteException : Exception
6869
{
70+
6971
public RemoteException(Error error) : base(error.Message, error.InnerError == null ? null : new RemoteException(error.InnerError))
7072
{
7173
Type = error.Type;
72-
StackTrace = error.StackTrace;
74+
SetRemoteStackTrace(error.StackTrace);
7375
}
76+
#if NETCOREAPP
77+
private void SetRemoteStackTrace(string stackTrace) => _ = ExceptionDispatchInfo.SetRemoteStackTrace(this, stackTrace);
78+
#else
79+
private const string StackTraceSeparator = "--- End of stack trace from previous location ---";
80+
private string _remoteStackTrace = null!;
81+
82+
private void SetRemoteStackTrace(string stackTrace) => _remoteStackTrace = stackTrace?.TrimEnd('\r', '\n') ?? "";
83+
public override string StackTrace => $"{_remoteStackTrace}\r\n{StackTraceSeparator}\r\n{base.StackTrace}";
84+
#endif
7485
public string Type { get; }
75-
public override string StackTrace { get; }
7686
public new RemoteException? InnerException => base.InnerException as RemoteException;
7787
public override string ToString()
7888
{

0 commit comments

Comments
 (0)