Skip to content

Commit d095896

Browse files
committed
More speaker tests
1 parent 634764c commit d095896

File tree

4 files changed

+567
-14
lines changed

4 files changed

+567
-14
lines changed

Coder.Desktop.sln.DotSettings

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,255 @@
11
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:String x:Key="/Default/CodeStyle/CSharpFileLayoutPatterns/Pattern/@EntryValue">&lt;Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"&gt;
3+
&lt;TypePattern DisplayName="Non-reorderable types" Priority="99999999"&gt;
4+
&lt;TypePattern.Match&gt;
5+
&lt;Or&gt;
6+
&lt;And&gt;
7+
&lt;Kind Is="Interface" /&gt;
8+
&lt;Or&gt;
9+
&lt;HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /&gt;
10+
&lt;HasAttribute Name="System.Runtime.InteropServices.ComImport" /&gt;
11+
&lt;/Or&gt;
12+
&lt;/And&gt;
13+
&lt;Kind Is="Struct" /&gt;
14+
&lt;HasAttribute Name="System.Runtime.InteropServices.StructLayoutAttribute" /&gt;
15+
&lt;HasAttribute Name="JetBrains.Annotations.NoReorderAttribute" /&gt;
16+
&lt;/Or&gt;
17+
&lt;/TypePattern.Match&gt;
18+
&lt;/TypePattern&gt;
19+
20+
&lt;TypePattern DisplayName="xUnit.net Test Classes" RemoveRegions="All"&gt;
21+
&lt;TypePattern.Match&gt;
22+
&lt;And&gt;
23+
&lt;Kind Is="Class" /&gt;
24+
&lt;HasMember&gt;
25+
&lt;And&gt;
26+
&lt;Kind Is="Method" /&gt;
27+
&lt;HasAttribute Name="Xunit.FactAttribute" Inherited="True" /&gt;
28+
&lt;HasAttribute Name="Xunit.TheoryAttribute" Inherited="True" /&gt;
29+
&lt;/And&gt;
30+
&lt;/HasMember&gt;
31+
&lt;/And&gt;
32+
&lt;/TypePattern.Match&gt;
33+
34+
&lt;Entry DisplayName="Fields"&gt;
35+
&lt;Entry.Match&gt;
36+
&lt;And&gt;
37+
&lt;Kind Is="Field" /&gt;
38+
&lt;Not&gt;
39+
&lt;Static /&gt;
40+
&lt;/Not&gt;
41+
&lt;/And&gt;
42+
&lt;/Entry.Match&gt;
43+
44+
&lt;Entry.SortBy&gt;
45+
&lt;Readonly /&gt;
46+
&lt;Name /&gt;
47+
&lt;/Entry.SortBy&gt;
48+
&lt;/Entry&gt;
49+
50+
&lt;Entry DisplayName="Constructors"&gt;
51+
&lt;Entry.Match&gt;
52+
&lt;Kind Is="Constructor" /&gt;
53+
&lt;/Entry.Match&gt;
54+
55+
&lt;Entry.SortBy&gt;
56+
&lt;Static/&gt;
57+
&lt;/Entry.SortBy&gt;
58+
&lt;/Entry&gt;
59+
60+
&lt;Entry DisplayName="Teardown Methods"&gt;
61+
&lt;Entry.Match&gt;
62+
&lt;And&gt;
63+
&lt;Kind Is="Method" /&gt;
64+
&lt;ImplementsInterface Name="System.IDisposable" /&gt;
65+
&lt;/And&gt;
66+
&lt;/Entry.Match&gt;
67+
&lt;/Entry&gt;
68+
69+
&lt;Entry DisplayName="All other members" /&gt;
70+
71+
&lt;Entry DisplayName="Test Methods" Priority="100"&gt;
72+
&lt;Entry.Match&gt;
73+
&lt;And&gt;
74+
&lt;Kind Is="Method" /&gt;
75+
&lt;HasAttribute Name="Xunit.FactAttribute" Inherited="false" /&gt;
76+
&lt;HasAttribute Name="Xunit.TheoryAttribute" Inherited="false" /&gt;
77+
&lt;/And&gt;
78+
&lt;/Entry.Match&gt;
79+
80+
&lt;Entry.SortBy&gt;
81+
&lt;Name /&gt;
82+
&lt;/Entry.SortBy&gt;
83+
&lt;/Entry&gt;
84+
&lt;/TypePattern&gt;
85+
86+
&lt;TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"&gt;
87+
&lt;TypePattern.Match&gt;
88+
&lt;And&gt;
89+
&lt;Kind Is="Class" /&gt;
90+
&lt;Or&gt;
91+
&lt;HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="true" /&gt;
92+
&lt;HasAttribute Name="NUnit.Framework.TestFixtureSourceAttribute" Inherited="true" /&gt;
93+
&lt;HasMember&gt;
94+
&lt;And&gt;
95+
&lt;Kind Is="Method" /&gt;
96+
&lt;HasAttribute Name="NUnit.Framework.TestAttribute" Inherited="false" /&gt;
97+
&lt;HasAttribute Name="NUnit.Framework.TestCaseAttribute" Inherited="false" /&gt;
98+
&lt;HasAttribute Name="NUnit.Framework.TestCaseSourceAttribute" Inherited="false" /&gt;
99+
&lt;/And&gt;
100+
&lt;/HasMember&gt;
101+
&lt;/Or&gt;
102+
&lt;/And&gt;
103+
&lt;/TypePattern.Match&gt;
104+
105+
&lt;Entry DisplayName="Setup/Teardown Methods"&gt;
106+
&lt;Entry.Match&gt;
107+
&lt;And&gt;
108+
&lt;Kind Is="Method" /&gt;
109+
&lt;Or&gt;
110+
&lt;HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="true" /&gt;
111+
&lt;HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="true" /&gt;
112+
&lt;HasAttribute Name="NUnit.Framework.TestFixtureSetUpAttribute" Inherited="true" /&gt;
113+
&lt;HasAttribute Name="NUnit.Framework.TestFixtureTearDownAttribute" Inherited="true" /&gt;
114+
&lt;HasAttribute Name="NUnit.Framework.OneTimeSetUpAttribute" Inherited="true" /&gt;
115+
&lt;HasAttribute Name="NUnit.Framework.OneTimeTearDownAttribute" Inherited="true" /&gt;
116+
&lt;/Or&gt;
117+
&lt;/And&gt;
118+
&lt;/Entry.Match&gt;
119+
&lt;/Entry&gt;
120+
121+
&lt;Entry DisplayName="All other members" /&gt;
122+
123+
&lt;Entry DisplayName="Test Methods" Priority="100"&gt;
124+
&lt;Entry.Match&gt;
125+
&lt;And&gt;
126+
&lt;Kind Is="Method" /&gt;
127+
&lt;HasAttribute Name="NUnit.Framework.TestAttribute" Inherited="false" /&gt;
128+
&lt;HasAttribute Name="NUnit.Framework.TestCaseAttribute" Inherited="false" /&gt;
129+
&lt;HasAttribute Name="NUnit.Framework.TestCaseSourceAttribute" Inherited="false" /&gt;
130+
&lt;/And&gt;
131+
&lt;/Entry.Match&gt;
132+
133+
&lt;Entry.SortBy&gt;
134+
&lt;Name /&gt;
135+
&lt;/Entry.SortBy&gt;
136+
&lt;/Entry&gt;
137+
&lt;/TypePattern&gt;
138+
139+
&lt;TypePattern DisplayName="Default Pattern"&gt;
140+
&lt;Entry DisplayName="Public Delegates" Priority="100"&gt;
141+
&lt;Entry.Match&gt;
142+
&lt;And&gt;
143+
&lt;Access Is="Public" /&gt;
144+
&lt;Kind Is="Delegate" /&gt;
145+
&lt;/And&gt;
146+
&lt;/Entry.Match&gt;
147+
148+
&lt;Entry.SortBy&gt;
149+
&lt;Name /&gt;
150+
&lt;/Entry.SortBy&gt;
151+
&lt;/Entry&gt;
152+
153+
&lt;Entry DisplayName="Public Enums" Priority="100"&gt;
154+
&lt;Entry.Match&gt;
155+
&lt;And&gt;
156+
&lt;Access Is="Public" /&gt;
157+
&lt;Kind Is="Enum" /&gt;
158+
&lt;/And&gt;
159+
&lt;/Entry.Match&gt;
160+
161+
&lt;Entry.SortBy&gt;
162+
&lt;Name /&gt;
163+
&lt;/Entry.SortBy&gt;
164+
&lt;/Entry&gt;
165+
166+
&lt;Entry DisplayName="Static Fields and Constants"&gt;
167+
&lt;Entry.Match&gt;
168+
&lt;Or&gt;
169+
&lt;Kind Is="Constant" /&gt;
170+
&lt;And&gt;
171+
&lt;Kind Is="Field" /&gt;
172+
&lt;Static /&gt;
173+
&lt;/And&gt;
174+
&lt;/Or&gt;
175+
&lt;/Entry.Match&gt;
176+
177+
&lt;Entry.SortBy&gt;
178+
&lt;Kind&gt;
179+
&lt;Kind.Order&gt;
180+
&lt;DeclarationKind&gt;Constant&lt;/DeclarationKind&gt;
181+
&lt;DeclarationKind&gt;Field&lt;/DeclarationKind&gt;
182+
&lt;/Kind.Order&gt;
183+
&lt;/Kind&gt;
184+
&lt;/Entry.SortBy&gt;
185+
&lt;/Entry&gt;
186+
187+
&lt;Entry DisplayName="Fields"&gt;
188+
&lt;Entry.Match&gt;
189+
&lt;And&gt;
190+
&lt;Kind Is="Field" /&gt;
191+
&lt;Not&gt;
192+
&lt;Static /&gt;
193+
&lt;/Not&gt;
194+
&lt;/And&gt;
195+
&lt;/Entry.Match&gt;
196+
197+
&lt;Entry.SortBy&gt;
198+
&lt;Readonly /&gt;
199+
&lt;Name /&gt;
200+
&lt;/Entry.SortBy&gt;
201+
&lt;/Entry&gt;
202+
203+
&lt;Entry DisplayName="Events"&gt;
204+
&lt;Entry.Match&gt;
205+
&lt;Kind Is="Event" /&gt;
206+
&lt;/Entry.Match&gt;
207+
208+
&lt;Entry.SortBy&gt;
209+
&lt;Name /&gt;
210+
&lt;/Entry.SortBy&gt;
211+
&lt;/Entry&gt;
212+
213+
&lt;Entry DisplayName="Constructors"&gt;
214+
&lt;Entry.Match&gt;
215+
&lt;Kind Is="Constructor" /&gt;
216+
&lt;/Entry.Match&gt;
217+
218+
&lt;Entry.SortBy&gt;
219+
&lt;Static/&gt;
220+
&lt;/Entry.SortBy&gt;
221+
&lt;/Entry&gt;
222+
223+
&lt;Entry DisplayName="Properties, Indexers"&gt;
224+
&lt;Entry.Match&gt;
225+
&lt;Or&gt;
226+
&lt;Kind Is="Property" /&gt;
227+
&lt;Kind Is="Indexer" /&gt;
228+
&lt;/Or&gt;
229+
&lt;/Entry.Match&gt;
230+
&lt;/Entry&gt;
231+
232+
&lt;Entry DisplayName="Interface Implementations" Priority="100"&gt;
233+
&lt;Entry.Match&gt;
234+
&lt;And&gt;
235+
&lt;Kind Is="Member" /&gt;
236+
&lt;ImplementsInterface /&gt;
237+
&lt;/And&gt;
238+
&lt;/Entry.Match&gt;
239+
240+
&lt;Entry.SortBy&gt;
241+
&lt;ImplementsInterface Immediate="true" /&gt;
242+
&lt;/Entry.SortBy&gt;
243+
&lt;/Entry&gt;
244+
245+
&lt;Entry DisplayName="All other members" /&gt;
246+
247+
&lt;Entry DisplayName="Nested Types"&gt;
248+
&lt;Entry.Match&gt;
249+
&lt;Kind Is="Type" /&gt;
250+
&lt;/Entry.Match&gt;
251+
&lt;/Entry&gt;
252+
&lt;/TypePattern&gt;
253+
&lt;/Patterns&gt;
254+
</s:String>
2255
<s:Boolean x:Key="/Default/UserDictionary/Words/=serdes/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

Rpc/Speaker.cs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections.Concurrent;
22
using System.Text;
33
using Coder.Desktop.Rpc.Proto;
4+
using Coder.Desktop.Rpc.Utilities;
45
using Google.Protobuf;
56

67
namespace Coder.Desktop.Rpc;
@@ -46,17 +47,6 @@ public class Speaker<TS, TR> : IAsyncDisposable
4647

4748
public delegate void OnReceiveDelegate(ReplyableRpcMessage<TS, TR> message);
4849

49-
/// <summary>
50-
/// Event that is triggered when a message is received.
51-
/// </summary>
52-
public event OnReceiveDelegate? Receive;
53-
54-
/// <summary>
55-
/// Event that is triggered when an error occurs. The handling code should dispose the Speaker after this event is
56-
/// triggered.
57-
/// </summary>
58-
public event OnErrorDelegate? Error;
59-
6050
private readonly Stream _conn;
6151

6252
// _cts is cancelled when Dispose is called and will cause all ongoing I/O
@@ -70,6 +60,17 @@ public class Speaker<TS, TR> : IAsyncDisposable
7060
private ulong _lastMessageId;
7161
private Task? _receiveTask;
7262

63+
/// <summary>
64+
/// Event that is triggered when an error occurs. The handling code should dispose the Speaker after this event is
65+
/// triggered.
66+
/// </summary>
67+
public event OnErrorDelegate? Error;
68+
69+
/// <summary>
70+
/// Event that is triggered when a message is received.
71+
/// </summary>
72+
public event OnReceiveDelegate? Receive;
73+
7374
/// <summary>
7475
/// Instantiates a speaker. The speaker will not perform any I/O until <c>StartAsync</c> is called.
7576
/// </summary>
@@ -111,9 +112,10 @@ private async Task PerformHandshake(CancellationToken ct = default)
111112
{
112113
// Simultaneously write the header string and read the header string in
113114
// case the conn is not buffered.
114-
var writeTask = WriteHeader(ct);
115-
var readTask = ReadHeader(ct);
116-
await Task.WhenAll(writeTask, readTask);
115+
var headerCts = CancellationTokenSource.CreateLinkedTokenSource(ct, _cts.Token);
116+
var writeTask = WriteHeader(headerCts.Token);
117+
var readTask = ReadHeader(headerCts.Token);
118+
await TaskUtilities.CancellableWhenAll(headerCts, writeTask, readTask);
117119

118120
var header = RpcHeader.Parse(await readTask);
119121
var expectedRole = RpcMessage<TR>.GetRole();

Rpc/Utilities/TaskUtilities.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
namespace Coder.Desktop.Rpc.Utilities;
2+
3+
internal static class TaskUtilities
4+
{
5+
/// <summary>
6+
/// Waits for all tasks to complete, but cancels the provided <c>CancellationTokenSource</c> if any task is canceled or
7+
/// faulted. The first cancel or fault will be propagated to the returned Task. All passed in tasks must be using the
8+
/// same <c>CancellationTokenSource</c>.
9+
/// The returned task will wait for all tasks to be completed.
10+
/// </summary>
11+
/// <example>
12+
/// <code lang="csharp">
13+
/// var cts = new CancellationTokenSource();
14+
/// var task1 = Task.Delay(1000, cts.Token);
15+
/// var task2 = Task.Delay(2000, cts.Token);
16+
/// await TaskUtilities.CancellableWhenAll(cts, task1, task2);
17+
/// </code>
18+
/// </example>
19+
/// <param name="tasks">Tasks to wait on</param>
20+
/// <param name="cts">The cancellation token source that was provided to each task</param>
21+
/// <returns>
22+
/// A task that completes when all tasks are completed, with the cancellation or exception state of the first
23+
/// non-successful task
24+
/// </returns>
25+
public static async Task CancellableWhenAll(CancellationTokenSource cts, params Task[] tasks)
26+
{
27+
var taskList = tasks.ToList();
28+
if (taskList.Count == 0) return;
29+
var tcs = new TaskCompletionSource();
30+
31+
var tasksWithCancellation = taskList.Select(task =>
32+
task.ContinueWith(t =>
33+
{
34+
if (t.IsFaulted)
35+
{
36+
cts.Cancel();
37+
tcs.TrySetException(t.Exception.InnerExceptions.First());
38+
}
39+
else if (t.IsCanceled)
40+
{
41+
cts.Cancel();
42+
tcs.TrySetCanceled();
43+
}
44+
}));
45+
46+
// Wait for all the task continuations to complete.
47+
try
48+
{
49+
await Task.WhenAll(tasksWithCancellation);
50+
tcs.TrySetResult();
51+
}
52+
catch
53+
{
54+
// Exception was already propagated.
55+
}
56+
57+
await tcs.Task;
58+
}
59+
}

0 commit comments

Comments
 (0)