Skip to content

Commit 1379847

Browse files
committed
reorganize test cases into separate files
1 parent 5252a44 commit 1379847

File tree

3 files changed

+1152
-1121
lines changed

3 files changed

+1152
-1121
lines changed
Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
2+
// SPDX-License-Identifier: MIT
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Net;
7+
using System.Net.Sockets;
8+
using System.IO;
9+
using System.Runtime.InteropServices;
10+
using System.Threading;
11+
using System.Threading.Tasks;
12+
using NUnit.Framework;
13+
using Smdn.Net.MuninPlugin;
14+
15+
namespace Smdn.Net.MuninNode;
16+
17+
[TestFixture]
18+
public partial class NodeBaseTests {
19+
private static Task StartSession(
20+
Func<NodeBase, TcpClient, StreamWriter, StreamReader, CancellationToken, Task> action
21+
)
22+
=> StartSession(
23+
accessRule: null,
24+
plugins: Array.Empty<IPlugin>(),
25+
action: action
26+
);
27+
28+
private static Task StartSession(
29+
IReadOnlyList<IPlugin> plugins,
30+
Func<NodeBase, TcpClient, StreamWriter, StreamReader, CancellationToken, Task> action
31+
)
32+
=> StartSession(
33+
accessRule: null,
34+
plugins: plugins,
35+
action: action
36+
);
37+
38+
private static async Task StartSession(
39+
IAccessRule? accessRule,
40+
IReadOnlyList<IPlugin> plugins,
41+
Func<NodeBase, TcpClient, StreamWriter, StreamReader, CancellationToken, Task> action
42+
)
43+
{
44+
await using var node = CreateNode(accessRule, plugins);
45+
46+
node.Start();
47+
48+
using var cts = new CancellationTokenSource(
49+
delay: TimeSpan.FromSeconds(5) // timeout for hung up
50+
);
51+
52+
var taskAccept = Task.Run(
53+
async () => await node.AcceptSingleSessionAsync(),
54+
cts.Token
55+
);
56+
57+
using var client = CreateClient((IPEndPoint)node.LocalEndPoint, out var writer, out var reader);
58+
59+
try {
60+
reader.ReadLine(); // banner
61+
62+
try {
63+
await action(node, client, writer, reader, cts.Token);
64+
}
65+
finally {
66+
client.Close();
67+
}
68+
}
69+
finally {
70+
await taskAccept;
71+
}
72+
}
73+
74+
[Test]
75+
public async Task AcceptSingleSessionAsync()
76+
{
77+
await using var node = CreateNode();
78+
79+
node.Start();
80+
81+
var taskAccept = Task.Run(async () => await node.AcceptSingleSessionAsync());
82+
83+
using var client = CreateClient((IPEndPoint)node.LocalEndPoint, out var writer, out var reader);
84+
85+
var banner = reader.ReadLine();
86+
87+
Assert.That(banner, Is.EqualTo($"# munin node at {node.HostName}"), nameof(banner));
88+
89+
writer.WriteLine(".");
90+
writer.Close();
91+
92+
Assert.DoesNotThrowAsync(async () => await taskAccept);
93+
}
94+
95+
[Test]
96+
public async Task AcceptSingleSessionAsync_NodeNotStarted()
97+
{
98+
await using var node = CreateNode();
99+
100+
Assert.ThrowsAsync<InvalidOperationException>(async () => await node.AcceptSingleSessionAsync());
101+
}
102+
103+
[TestCase(0)]
104+
[TestCase(1)]
105+
[TestCase(1000)]
106+
public async Task AcceptSingleSessionAsync_CancellationRequested(int delayMilliseconds)
107+
{
108+
await using var node = CreateNode();
109+
110+
node.Start();
111+
112+
using var cts = new CancellationTokenSource(millisecondsDelay: delayMilliseconds);
113+
114+
var ex = Assert.CatchAsync(async () => await node.AcceptSingleSessionAsync(cts.Token));
115+
116+
Assert.That(ex, Is.InstanceOf<OperationCanceledException>().Or.InstanceOf<TaskCanceledException>());
117+
}
118+
119+
[Test]
120+
public async Task AcceptSingleSessionAsync_ClientDisconnected_BeforeSendingBanner()
121+
{
122+
await using var node = CreateNode();
123+
124+
node.Start();
125+
126+
var taskAccept = Task.Run(async () => await node.AcceptSingleSessionAsync());
127+
128+
using var client = CreateClient((IPEndPoint)node.LocalEndPoint, out _, out _);
129+
130+
client.Close();
131+
132+
Assert.DoesNotThrowAsync(async () => await taskAccept);
133+
}
134+
135+
[Test]
136+
public async Task AcceptSingleSessionAsync_ClientDisconnected_WhileAwaitingCommand()
137+
{
138+
await using var node = CreateNode();
139+
140+
node.Start();
141+
142+
var taskAccept = Task.Run(async () => await node.AcceptSingleSessionAsync());
143+
144+
using var client = CreateClient((IPEndPoint)node.LocalEndPoint, out _, out var reader);
145+
146+
reader.ReadLine(); // read banner
147+
148+
client.Close();
149+
150+
Assert.DoesNotThrowAsync(async () => await taskAccept);
151+
}
152+
153+
private sealed class AcceptAllAccessRule : IAccessRule {
154+
public bool IsAcceptable(IPEndPoint remoteEndPoint) => true;
155+
}
156+
157+
[Test]
158+
public async Task AcceptSingleSessionAsync_IAccessRule_AccessGranted()
159+
{
160+
await StartSession(
161+
accessRule: new AcceptAllAccessRule(),
162+
plugins: Array.Empty<IPlugin>(),
163+
async static (node, client, writer, reader, cancellationToken
164+
) => {
165+
await writer.WriteLineAsync("command", cancellationToken);
166+
await writer.FlushAsync(cancellationToken);
167+
168+
Assert.That(
169+
await reader.ReadLineAsync(cancellationToken),
170+
Is.Not.Null,
171+
"line #1"
172+
);
173+
174+
var connected = !(
175+
client.Client.Poll(1 /*microsecs*/, SelectMode.SelectRead) &&
176+
client.Client.Available == 0
177+
);
178+
179+
Assert.That(connected, Is.True);
180+
});
181+
}
182+
183+
private sealed class RefuseAllAccessRule : IAccessRule {
184+
public bool IsAcceptable(IPEndPoint remoteEndPoint) => false;
185+
}
186+
187+
[Test]
188+
public async Task AcceptSingleSessionAsync_IAccessRule_AccessRefused()
189+
{
190+
await StartSession(
191+
accessRule: new RefuseAllAccessRule(),
192+
plugins: Array.Empty<IPlugin>(),
193+
async static (node, client, writer, reader, cancellationToken
194+
) => {
195+
await writer.WriteLineAsync(".", cancellationToken);
196+
await writer.FlushAsync(cancellationToken);
197+
198+
try {
199+
Assert.That(
200+
await reader.ReadLineAsync(cancellationToken),
201+
Is.Null,
202+
"line #1"
203+
);
204+
}
205+
catch (IOException ex) when (
206+
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
207+
ex.InnerException is SocketException
208+
) {
209+
// ignore
210+
}
211+
212+
var connected = !(
213+
client.Client.Poll(1 /*microsecs*/, SelectMode.SelectRead) &&
214+
client.Client.Available == 0
215+
);
216+
217+
Assert.That(connected, Is.False);
218+
});
219+
}
220+
221+
private class PseudoPluginWithSessionCallback : IPlugin, INodeSessionCallback {
222+
public string Name => throw new NotImplementedException();
223+
public IPluginGraphAttributes GraphAttributes => throw new NotImplementedException();
224+
public IPluginDataSource DataSource => throw new NotImplementedException();
225+
226+
private readonly bool setSessionCallbackNull;
227+
public INodeSessionCallback? SessionCallback => setSessionCallbackNull ? null : this;
228+
public List<string> StartedSessionIds { get; } = new();
229+
public List<string> ClosedSessionIds { get; } = new();
230+
231+
public PseudoPluginWithSessionCallback(bool setSessionCallbackNull)
232+
{
233+
this.setSessionCallbackNull = setSessionCallbackNull;
234+
}
235+
236+
public ValueTask ReportSessionStartedAsync(string sessionId, CancellationToken cancellationToken)
237+
{
238+
StartedSessionIds.Add(sessionId);
239+
240+
return default;
241+
}
242+
243+
public ValueTask ReportSessionClosedAsync(string sessionId, CancellationToken cancellationToken)
244+
{
245+
ClosedSessionIds.Add(sessionId);
246+
247+
return default;
248+
}
249+
}
250+
251+
[TestCase(true)]
252+
[TestCase(false)]
253+
public async Task AcceptSingleSessionAsync_INodeSessionCallback(bool setSessionCallbackNull)
254+
{
255+
var plugin = new PseudoPluginWithSessionCallback(setSessionCallbackNull);
256+
var isSessionCallbackNull = plugin.SessionCallback is null;
257+
258+
Assert.That(isSessionCallbackNull, Is.EqualTo(setSessionCallbackNull));
259+
260+
await using var node = CreateNode(plugins: new IPlugin[] { plugin });
261+
262+
node.Start();
263+
264+
var taskAccept = Task.Run(async () => await node.AcceptSingleSessionAsync());
265+
266+
Assert.That(plugin.StartedSessionIds.Count, Is.EqualTo(0), nameof(plugin.StartedSessionIds));
267+
Assert.That(plugin.ClosedSessionIds.Count, Is.EqualTo(0), nameof(plugin.ClosedSessionIds));
268+
269+
using var client = CreateClient((IPEndPoint)node.LocalEndPoint, out var writer, out var reader);
270+
271+
var banner = reader.ReadLine();
272+
273+
Assert.That(banner, Is.EqualTo($"# munin node at {node.HostName}"), nameof(banner));
274+
275+
await Task.Delay(500); // wait for node process completed
276+
277+
if (isSessionCallbackNull) {
278+
Assert.That(plugin.StartedSessionIds.Count, Is.EqualTo(0), nameof(plugin.StartedSessionIds));
279+
Assert.That(plugin.ClosedSessionIds.Count, Is.EqualTo(0), nameof(plugin.ClosedSessionIds));
280+
}
281+
else {
282+
Assert.That(plugin.StartedSessionIds.Count, Is.EqualTo(1), nameof(plugin.StartedSessionIds));
283+
Assert.That(plugin.StartedSessionIds[0], Is.Not.Empty, nameof(plugin.StartedSessionIds));
284+
285+
Assert.That(plugin.ClosedSessionIds.Count, Is.EqualTo(0), nameof(plugin.ClosedSessionIds));
286+
}
287+
288+
writer.WriteLine(".");
289+
writer.Close();
290+
291+
Assert.DoesNotThrowAsync(async () => await taskAccept);
292+
293+
if (isSessionCallbackNull) {
294+
Assert.That(plugin.StartedSessionIds.Count, Is.EqualTo(0), nameof(plugin.StartedSessionIds));
295+
Assert.That(plugin.ClosedSessionIds.Count, Is.EqualTo(0), nameof(plugin.ClosedSessionIds));
296+
}
297+
else {
298+
Assert.That(plugin.StartedSessionIds.Count, Is.EqualTo(1), nameof(plugin.StartedSessionIds));
299+
300+
Assert.That(plugin.ClosedSessionIds.Count, Is.EqualTo(1), nameof(plugin.ClosedSessionIds));
301+
Assert.That(plugin.ClosedSessionIds[0], Is.Not.Empty, nameof(plugin.ClosedSessionIds));
302+
}
303+
}
304+
305+
[TestCase(true)]
306+
[TestCase(false)]
307+
public async Task AcceptAsync(bool throwIfCancellationRequested)
308+
{
309+
await using var node = CreateNode();
310+
311+
node.Start();
312+
313+
using var cts = new CancellationTokenSource();
314+
315+
var taskAccept = Task.Run(async () => await node.AcceptAsync(throwIfCancellationRequested, cts.Token));
316+
317+
using var client0 = CreateClient((IPEndPoint)node.LocalEndPoint, out var writer0, out var reader0);
318+
319+
reader0.ReadLine();
320+
writer0.WriteLine(".");
321+
writer0.Close();
322+
323+
Assert.That(taskAccept.Wait(TimeSpan.FromSeconds(1.0)), Is.False, "task must not be completed");
324+
325+
using var client1 = CreateClient((IPEndPoint)node.LocalEndPoint, out var writer1, out var reader1);
326+
327+
reader1.ReadLine();
328+
writer1.WriteLine(".");
329+
writer1.Close();
330+
331+
Assert.That(taskAccept.Wait(TimeSpan.FromSeconds(1.0)), Is.False, "task must not be completed");
332+
333+
cts.Cancel();
334+
335+
if (throwIfCancellationRequested)
336+
Assert.ThrowsAsync<OperationCanceledException>(async () => await taskAccept);
337+
else
338+
Assert.DoesNotThrowAsync(async () => await taskAccept);
339+
}
340+
}

0 commit comments

Comments
 (0)