Skip to content

Commit 7efa5bf

Browse files
#450 - Make Tag testable without devices.
1 parent cd1f581 commit 7efa5bf

File tree

7 files changed

+329
-127
lines changed

7 files changed

+329
-127
lines changed

src/libplctag.Tests/MyUdt.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.Runtime.InteropServices;
2+
3+
namespace libplctag.Tests
4+
{
5+
[StructLayout(LayoutKind.Sequential, Pack = 4)]
6+
public struct MyUdt
7+
{
8+
public short shortField;
9+
public int intField;
10+
}
11+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
namespace libplctag.Tests
5+
{
6+
public static class StructMarshallingExtensions
7+
{
8+
/// <summary>
9+
/// Converts a blittable struct to a byte array.
10+
/// </summary>
11+
public static byte[] ToByteArray<T>(this T value) where T : struct
12+
{
13+
byte[] bytes = new byte[Marshal.SizeOf<T>()];
14+
MemoryMarshal.Write(bytes, ref value); // zero-allocation, fast
15+
return bytes;
16+
}
17+
18+
/// <summary>
19+
/// Reads a blittable struct from a byte array.
20+
/// </summary>
21+
public static T ToStruct<T>(this byte[] bytes) where T : struct
22+
{
23+
if (bytes.Length < Marshal.SizeOf<T>())
24+
throw new ArgumentException($"Byte array too small for struct {typeof(T)}");
25+
26+
return MemoryMarshal.Read<T>(bytes);
27+
}
28+
}
29+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
namespace libplctag.Tests
5+
{
6+
public static class TagExtensions
7+
{
8+
public static T GetValue<T>(this Tag tag) where T : struct
9+
{
10+
byte[] buffer = tag.GetBuffer();
11+
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
12+
13+
try
14+
{
15+
//ToInt64 is used because it's assumed the code will compile and run on 64-bit platform.
16+
//If it's going to run on a 32-platform, ToInt32 should be used.
17+
T retVal = Marshal.PtrToStructure<T>(new IntPtr(handle.AddrOfPinnedObject().ToInt64()))!;
18+
19+
return (retVal);
20+
}
21+
finally
22+
{
23+
handle.Free();
24+
}
25+
}
26+
}
27+
}

src/libplctag.Tests/TagTests.cs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using FluentAssertions;
2+
using libplctag.NativeImport;
3+
using Moq;
4+
using System;
5+
using System.Net;
6+
using System.Runtime.InteropServices;
7+
using Xunit;
8+
9+
namespace libplctag.Tests
10+
{
11+
public class TagTests : IDisposable
12+
{
13+
private const int TimeoutInMilliSeconds = 1000;
14+
private readonly MockRepository _mockRepository;
15+
private readonly Mock<INative> _iNativeMock;
16+
private readonly Tag _underTest;
17+
18+
19+
public TagTests()
20+
{
21+
_mockRepository = new MockRepository(MockBehavior.Strict);
22+
_iNativeMock = _mockRepository.Create<INative>();
23+
_underTest = new Tag(_iNativeMock.Object);
24+
_underTest.Timeout = TimeSpan.FromMilliseconds(TimeoutInMilliSeconds);
25+
}
26+
27+
[Fact]
28+
public void TagCanBeInitialized()
29+
{
30+
// ARRANGE
31+
int nativeTagHandle = 1;
32+
GivenTagCanBeInitializedWithHandle(nativeTagHandle);
33+
34+
// ACT
35+
_underTest.Initialize();
36+
37+
38+
// ASSERT
39+
_underTest.IsInitialized.Should().BeTrue();
40+
_underTest.NativeTagHandle.Should().Be(nativeTagHandle);
41+
}
42+
43+
private void GivenTagCanBeInitializedWithHandle(int nativeTagHandle)
44+
{
45+
_iNativeMock.Setup(native => native.plc_tag_create_ex(
46+
It.IsAny<string>(),
47+
It.IsAny<plctag.callback_func_ex>(),
48+
IntPtr.Zero,
49+
TimeoutInMilliSeconds))
50+
.Returns(nativeTagHandle);
51+
}
52+
53+
54+
[Fact]
55+
public void TagForMyUdtShouldReturnMockedValue()
56+
{
57+
// ARRANGE
58+
int nativeTagHandle = 1;
59+
MyUdt expectedValue = new() { intField = 1, shortField = 2 };
60+
_underTest.ElementSize = Marshal.SizeOf(typeof(MyUdt));
61+
62+
GivenTagCanBeInitializedWithHandle(nativeTagHandle);
63+
GivenMarshalledDataIsReturnedInBuffer(nativeTagHandle, expectedValue);
64+
65+
// ACT
66+
_underTest.Initialize();
67+
MyUdt currentTagValue = _underTest.GetValue<MyUdt>();
68+
69+
// ASSERT
70+
currentTagValue.Should().Be(expectedValue);
71+
}
72+
73+
private void GivenMarshalledDataIsReturnedInBuffer<T>(int nativeTagHandle, T expectedData) where T : struct
74+
{
75+
byte[] byteData = expectedData.ToByteArray();
76+
int size = Marshal.SizeOf(typeof(T));
77+
78+
_iNativeMock.Setup(native => native.plc_tag_get_size(nativeTagHandle)).Returns(size);
79+
_iNativeMock.Setup(native => native.plc_tag_get_raw_bytes(nativeTagHandle, 0, It.IsAny<byte[]>(), size))
80+
.Returns((int)Status.Ok)
81+
.Callback((int tag, int start_offset, byte[] buffer, int buffer_length) =>
82+
{
83+
Array.Copy(byteData, buffer, byteData.Length);
84+
});
85+
}
86+
87+
88+
public void Dispose()
89+
{
90+
_mockRepository.VerifyAll();
91+
GC.SuppressFinalize(this);
92+
}
93+
}
94+
}

src/libplctag.Tests/libplctag.Tests.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10+
<PackageReference Include="FluentAssertions" Version="8.6.0" />
1011
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
1112
<PackageReference Include="Moq" Version="4.20.70" />
1213
<PackageReference Include="xunit" Version="2.9.0" />
@@ -24,4 +25,8 @@
2425
<ProjectReference Include="..\libplctag\libplctag.csproj" />
2526
</ItemGroup>
2627

28+
<ItemGroup>
29+
<Compile Remove="LogixString.cs" />
30+
</ItemGroup>
31+
2732
</Project>

src/libplctag/INative.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
namespace libplctag
1818
{
19-
interface INative
19+
public interface INative
2020
{
2121
int plc_tag_abort(int tag);
2222
int plc_tag_check_lib_version(int req_major, int req_minor, int req_patch);

0 commit comments

Comments
 (0)