Skip to content

Commit b475aee

Browse files
authored
Merge pull request #401 from scamille/test-ConnectionClose
Add unit test to ensure a connection has been properly closed
2 parents 37c63ce + 82a745c commit b475aee

File tree

1 file changed

+181
-0
lines changed

1 file changed

+181
-0
lines changed
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
using Microsoft.VisualStudio.TestTools.UnitTesting;
2+
using System;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Net.Sockets;
6+
using System.Reflection;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
10+
namespace S7.Net.UnitTest
11+
{
12+
/// <summary>
13+
/// Test stream which only gives 1 byte per read.
14+
/// </summary>
15+
class TestStreamConnectionClose : Stream
16+
{
17+
private readonly CancellationTokenSource _cancellationTokenSource;
18+
19+
public TestStreamConnectionClose(CancellationTokenSource cancellationTokenSource)
20+
{
21+
_cancellationTokenSource = cancellationTokenSource;
22+
}
23+
public override bool CanRead => false;
24+
25+
public override bool CanSeek => throw new NotImplementedException();
26+
27+
public override bool CanWrite => true;
28+
29+
public override long Length => throw new NotImplementedException();
30+
31+
public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
32+
33+
public override void Flush()
34+
{
35+
throw new NotImplementedException();
36+
}
37+
38+
public override int Read(byte[] buffer, int offset, int count)
39+
{
40+
throw new NotImplementedException();
41+
}
42+
43+
public override long Seek(long offset, SeekOrigin origin)
44+
{
45+
throw new NotImplementedException();
46+
}
47+
48+
public override void SetLength(long value)
49+
{
50+
throw new NotImplementedException();
51+
}
52+
53+
public override void Write(byte[] buffer, int offset, int count)
54+
{
55+
_cancellationTokenSource.Cancel();
56+
}
57+
}
58+
59+
/// <summary>
60+
/// These tests are intended to test <see cref="StreamExtensions"/> functions and other stream-related special cases.
61+
/// </summary>
62+
[TestClass]
63+
public class ConnectionCloseTest
64+
{
65+
const short TestServerPort = 31122;
66+
const string TestServerIp = "127.0.0.1";
67+
68+
[TestMethod]
69+
public async Task Test_CancellationDuringTransmission()
70+
{
71+
var plc = new Plc(CpuType.S7300, TestServerIp, TestServerPort, 0, 2);
72+
73+
// Set up a shared cancellation source so we can let the stream
74+
// initiate cancel after some data has been written to it.
75+
var cancellationSource = new CancellationTokenSource();
76+
var cancellationToken = cancellationSource.Token;
77+
78+
var stream = new TestStreamConnectionClose(cancellationSource);
79+
var requestData = new byte[100]; // empty data, it does not matter what is in there
80+
81+
// Set up access to private method and field
82+
var dynMethod = plc.GetType().GetMethod("NoLockRequestTpduAsync",
83+
BindingFlags.NonPublic | BindingFlags.Instance);
84+
if (dynMethod == null)
85+
{
86+
throw new NullReferenceException("Could not find method 'NoLockRequestTpduAsync' on Plc object.");
87+
}
88+
var tcpClientField = plc.GetType().GetField("tcpClient", BindingFlags.NonPublic | BindingFlags.Instance);
89+
if (tcpClientField == null)
90+
{
91+
throw new NullReferenceException("Could not find field 'tcpClient' on Plc object.");
92+
}
93+
94+
// Set a value to tcpClient field so we can later ensure that it has been closed.
95+
tcpClientField.SetValue(plc, new TcpClient());
96+
var tcpClientValue = tcpClientField.GetValue(plc);
97+
Assert.IsNotNull(tcpClientValue);
98+
99+
try
100+
{
101+
var result = (Task<COTP.TPDU>) dynMethod.Invoke(plc, new object[] { stream, requestData, cancellationToken });
102+
await result;
103+
}
104+
catch (OperationCanceledException)
105+
{
106+
Console.WriteLine("Task was cancelled as expected.");
107+
108+
// Ensure that the plc connection was closed since the task was cancelled
109+
// after data has been sent through the network. We expect that the tcpClient
110+
// object was set to NULL
111+
var tcpClientValueAfter = tcpClientField.GetValue(plc);
112+
Assert.IsNull(tcpClientValueAfter);
113+
return;
114+
}
115+
catch (Exception e)
116+
{
117+
Assert.Fail($"Wrong exception type received. Expected {typeof(OperationCanceledException)}, received {e.GetType()}.");
118+
}
119+
120+
// Ensure test fails if cancellation did not occur.
121+
Assert.Fail("Task was not cancelled as expected.");
122+
}
123+
124+
[TestMethod]
125+
public async Task Test_CancellationBeforeTransmission()
126+
{
127+
var plc = new Plc(CpuType.S7300, TestServerIp, TestServerPort, 0, 2);
128+
129+
// Set up a cancellation source
130+
var cancellationSource = new CancellationTokenSource();
131+
var cancellationToken = cancellationSource.Token;
132+
133+
var stream = new TestStreamConnectionClose(cancellationSource);
134+
var requestData = new byte[100]; // empty data, it does not matter what is in there
135+
136+
// Set up access to private method and field
137+
var dynMethod = plc.GetType().GetMethod("NoLockRequestTpduAsync",
138+
BindingFlags.NonPublic | BindingFlags.Instance);
139+
if (dynMethod == null)
140+
{
141+
throw new NullReferenceException("Could not find method 'NoLockRequestTpduAsync' on Plc object.");
142+
}
143+
var tcpClientField = plc.GetType().GetField("tcpClient", BindingFlags.NonPublic | BindingFlags.Instance);
144+
if (tcpClientField == null)
145+
{
146+
throw new NullReferenceException("Could not find field 'tcpClient' on Plc object.");
147+
}
148+
149+
// Set a value to tcpClient field so we can later ensure that it has been closed.
150+
tcpClientField.SetValue(plc, new TcpClient());
151+
var tcpClientValue = tcpClientField.GetValue(plc);
152+
Assert.IsNotNull(tcpClientValue);
153+
154+
try
155+
{
156+
// cancel the task before we start transmitting data
157+
cancellationSource.Cancel();
158+
var result = (Task<COTP.TPDU>)dynMethod.Invoke(plc, new object[] { stream, requestData, cancellationToken });
159+
await result;
160+
}
161+
catch (OperationCanceledException)
162+
{
163+
Console.WriteLine("Task was cancelled as expected.");
164+
165+
// Ensure that the plc connection was not closed, since we cancelled the task before
166+
// sending data through the network. We expect that the tcpClient
167+
// object was NOT set to NULL
168+
var tcpClientValueAfter = tcpClientField.GetValue(plc);
169+
Assert.IsNotNull(tcpClientValueAfter);
170+
return;
171+
}
172+
catch (Exception e)
173+
{
174+
Assert.Fail($"Wrong exception type received. Expected {typeof(OperationCanceledException)}, received {e.GetType()}.");
175+
}
176+
177+
// Ensure test fails if cancellation did not occur.
178+
Assert.Fail("Task was not cancelled as expected.");
179+
}
180+
}
181+
}

0 commit comments

Comments
 (0)