-
Notifications
You must be signed in to change notification settings - Fork 601
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Release highlights: - Change default TSAP for S7 200 - Add S7 200 Smart support - Add support for custom TSAP's - Align data to even bytes when parsing responses from class and struct reads - Close connection on IOException - Add cancellation for Read/Write - Set default Read-/WriteTimeout to 10 seconds - Cleanup of sync helper methods
- Loading branch information
Showing
17 changed files
with
575 additions
and
208 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
name: Test | ||
|
||
on: | ||
[pull_request, push] | ||
|
||
jobs: | ||
|
||
build_test: | ||
name: test-${{ matrix.os }}-${{ matrix.test-framework }} | ||
runs-on: ${{ matrix.os }} | ||
env: | ||
configuration: Release | ||
artifacts: ${{ github.workspace }}/artifacts | ||
DOTNET_NOLOGO : 1 | ||
strategy: | ||
matrix: | ||
os: [windows-latest, ubuntu-latest, macos-latest] | ||
test-framework: [netcoreapp3.1, net5.0] | ||
include: | ||
- os: ubuntu-latest | ||
test-framework: netcoreapp3.1 | ||
installSnap7: true | ||
dotnet-sdk: '3.1.x' | ||
- os: ubuntu-latest | ||
test-framework: net5.0 | ||
installSnap7: true | ||
dotnet-sdk: '5.0.x' | ||
- os: macos-latest | ||
test-framework: netcoreapp3.1 | ||
installSnap7: true | ||
dotnet-sdk: '3.1.x' | ||
- os: macos-latest | ||
test-framework: net5.0 | ||
installSnap7: true | ||
dotnet-sdk: '5.0.x' | ||
- os: windows-latest | ||
test-framework: netcoreapp3.1 | ||
dotnet-sdk: '3.1.x' | ||
- os: windows-latest | ||
test-framework: net5.0 | ||
dotnet-sdk: '5.0.x' | ||
- os: windows-latest | ||
test-framework: net452 | ||
dotnet-sdk: '5.0.x' | ||
fail-fast: false | ||
|
||
steps: | ||
- uses: actions/checkout@v2 | ||
|
||
- name: Install Snap7 Linux | ||
if: ${{ matrix.installSnap7 && matrix.os == 'ubuntu-latest' }} | ||
run: | | ||
sudo add-apt-repository ppa:gijzelaar/snap7 | ||
sudo apt-get update | ||
sudo apt-get install libsnap7-1 libsnap7-dev | ||
- name: Install Snap7 MacOs | ||
if: ${{ matrix.installSnap7 && matrix.os == 'macos-latest' }} | ||
run: | | ||
brew install snap7 | ||
- name: Setup Dotnet | ||
uses: actions/setup-dotnet@v1 | ||
with: | ||
dotnet-version: ${{ matrix.dotnet-sdk }} | ||
|
||
- name: Nuget Cache | ||
uses: actions/cache@v2 | ||
with: | ||
path: ~/.nuget/packages | ||
# Look to see if there is a cache hit for the corresponding requirements file | ||
key: ${{ runner.os }}-${{ matrix.test-framework }}-nuget-${{ hashFiles('**/packages.lock.json') }} | ||
restore-keys: | | ||
${{ runner.os }}-${{ matrix.test-framework }}-nuget | ||
- name: Restore | ||
run: dotnet restore S7.Net.UnitTest | ||
|
||
- name: Test | ||
run: dotnet test --no-restore --nologo --verbosity normal --logger GitHubActions --framework ${{ matrix.test-framework }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using System; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Net.Sockets; | ||
using System.Reflection; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace S7.Net.UnitTest | ||
{ | ||
/// <summary> | ||
/// Test stream which only gives 1 byte per read. | ||
/// </summary> | ||
class TestStreamConnectionClose : Stream | ||
{ | ||
private readonly CancellationTokenSource _cancellationTokenSource; | ||
|
||
public TestStreamConnectionClose(CancellationTokenSource cancellationTokenSource) | ||
{ | ||
_cancellationTokenSource = cancellationTokenSource; | ||
} | ||
public override bool CanRead => false; | ||
|
||
public override bool CanSeek => throw new NotImplementedException(); | ||
|
||
public override bool CanWrite => true; | ||
|
||
public override long Length => throw new NotImplementedException(); | ||
|
||
public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } | ||
|
||
public override void Flush() | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
|
||
public override int Read(byte[] buffer, int offset, int count) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
|
||
public override long Seek(long offset, SeekOrigin origin) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
|
||
public override void SetLength(long value) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
|
||
public override void Write(byte[] buffer, int offset, int count) | ||
{ | ||
_cancellationTokenSource.Cancel(); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// These tests are intended to test <see cref="StreamExtensions"/> functions and other stream-related special cases. | ||
/// </summary> | ||
[TestClass] | ||
public class ConnectionCloseTest | ||
{ | ||
const short TestServerPort = 31122; | ||
const string TestServerIp = "127.0.0.1"; | ||
|
||
[TestMethod] | ||
public async Task Test_CancellationDuringTransmission() | ||
{ | ||
var plc = new Plc(CpuType.S7300, TestServerIp, TestServerPort, 0, 2); | ||
|
||
// Set up a shared cancellation source so we can let the stream | ||
// initiate cancel after some data has been written to it. | ||
var cancellationSource = new CancellationTokenSource(); | ||
var cancellationToken = cancellationSource.Token; | ||
|
||
var stream = new TestStreamConnectionClose(cancellationSource); | ||
var requestData = new byte[100]; // empty data, it does not matter what is in there | ||
|
||
// Set up access to private method and field | ||
var dynMethod = plc.GetType().GetMethod("NoLockRequestTpduAsync", | ||
BindingFlags.NonPublic | BindingFlags.Instance); | ||
if (dynMethod == null) | ||
{ | ||
throw new NullReferenceException("Could not find method 'NoLockRequestTpduAsync' on Plc object."); | ||
} | ||
var tcpClientField = plc.GetType().GetField("tcpClient", BindingFlags.NonPublic | BindingFlags.Instance); | ||
if (tcpClientField == null) | ||
{ | ||
throw new NullReferenceException("Could not find field 'tcpClient' on Plc object."); | ||
} | ||
|
||
// Set a value to tcpClient field so we can later ensure that it has been closed. | ||
tcpClientField.SetValue(plc, new TcpClient()); | ||
var tcpClientValue = tcpClientField.GetValue(plc); | ||
Assert.IsNotNull(tcpClientValue); | ||
|
||
try | ||
{ | ||
var result = (Task<COTP.TPDU>) dynMethod.Invoke(plc, new object[] { stream, requestData, cancellationToken }); | ||
await result; | ||
} | ||
catch (OperationCanceledException) | ||
{ | ||
Console.WriteLine("Task was cancelled as expected."); | ||
|
||
// Ensure that the plc connection was closed since the task was cancelled | ||
// after data has been sent through the network. We expect that the tcpClient | ||
// object was set to NULL | ||
var tcpClientValueAfter = tcpClientField.GetValue(plc); | ||
Assert.IsNull(tcpClientValueAfter); | ||
return; | ||
} | ||
catch (Exception e) | ||
{ | ||
Assert.Fail($"Wrong exception type received. Expected {typeof(OperationCanceledException)}, received {e.GetType()}."); | ||
} | ||
|
||
// Ensure test fails if cancellation did not occur. | ||
Assert.Fail("Task was not cancelled as expected."); | ||
} | ||
|
||
[TestMethod] | ||
public async Task Test_CancellationBeforeTransmission() | ||
{ | ||
var plc = new Plc(CpuType.S7300, TestServerIp, TestServerPort, 0, 2); | ||
|
||
// Set up a cancellation source | ||
var cancellationSource = new CancellationTokenSource(); | ||
var cancellationToken = cancellationSource.Token; | ||
|
||
var stream = new TestStreamConnectionClose(cancellationSource); | ||
var requestData = new byte[100]; // empty data, it does not matter what is in there | ||
|
||
// Set up access to private method and field | ||
var dynMethod = plc.GetType().GetMethod("NoLockRequestTpduAsync", | ||
BindingFlags.NonPublic | BindingFlags.Instance); | ||
if (dynMethod == null) | ||
{ | ||
throw new NullReferenceException("Could not find method 'NoLockRequestTpduAsync' on Plc object."); | ||
} | ||
var tcpClientField = plc.GetType().GetField("tcpClient", BindingFlags.NonPublic | BindingFlags.Instance); | ||
if (tcpClientField == null) | ||
{ | ||
throw new NullReferenceException("Could not find field 'tcpClient' on Plc object."); | ||
} | ||
|
||
// Set a value to tcpClient field so we can later ensure that it has been closed. | ||
tcpClientField.SetValue(plc, new TcpClient()); | ||
var tcpClientValue = tcpClientField.GetValue(plc); | ||
Assert.IsNotNull(tcpClientValue); | ||
|
||
try | ||
{ | ||
// cancel the task before we start transmitting data | ||
cancellationSource.Cancel(); | ||
var result = (Task<COTP.TPDU>)dynMethod.Invoke(plc, new object[] { stream, requestData, cancellationToken }); | ||
await result; | ||
} | ||
catch (OperationCanceledException) | ||
{ | ||
Console.WriteLine("Task was cancelled as expected."); | ||
|
||
// Ensure that the plc connection was not closed, since we cancelled the task before | ||
// sending data through the network. We expect that the tcpClient | ||
// object was NOT set to NULL | ||
var tcpClientValueAfter = tcpClientField.GetValue(plc); | ||
Assert.IsNotNull(tcpClientValueAfter); | ||
return; | ||
} | ||
catch (Exception e) | ||
{ | ||
Assert.Fail($"Wrong exception type received. Expected {typeof(OperationCanceledException)}, received {e.GetType()}."); | ||
} | ||
|
||
// Ensure test fails if cancellation did not occur. | ||
Assert.Fail("Task was not cancelled as expected."); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.