Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions tests/StdUnit.Tags.Tests/S7Tags/S7BitTagCbntorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using StdUnit.Tags.S7;
using System;
using Xunit;

namespace StdUnit.Tags.Tests.S7Tags;

/// <summary>
/// <see cref="S7BitTagCbntor"/> 的取值/赋值:按 <see cref="TagCbntor.CacheOffset"/> 定位缓存字节的第 NthBit 位,
/// 只改动目标位、保留同字节其它位,并打上脏标记。
/// </summary>
public class S7BitTagCbntorTests
{
private static (S7TagCbnt Cbnt, S7BitTagCbntor Tag) Create(int cacheSize = 4, int tagOffset = 1, int cacheOffset = 1, byte nthBit = 3)
{
var cbnt = new S7TagCbnt(new TagCbntDescriptor { Name = "cbnt1", StartAddress = "DB200.100" });
cbnt.ResizeCache(cacheSize);

var descriptor = new TagDescriptor
{
TagName = "bit-tag",
TagKind = BuiltinTagKinds.BIT,
RawAddress = $"DB200.{100 + tagOffset}.{nthBit}",
TagSize = 1,
};
var tag = new S7BitTagCbntor(descriptor, cbnt, tagOffset, cacheOffset, nthBit);
return (cbnt, tag);
}

[Fact]
public void Value_SetTrue_ShouldSetTargetBitAndMarkDirty()
{
var (cbnt, tag) = Create(cacheOffset: 1, nthBit: 3);

tag.Value = true;

Assert.Equal(0b0000_1000, cbnt.Cache.Span[1]);
Assert.True(tag.IsDirty);
Assert.True(cbnt.IsDirty);
Assert.NotEqual(default, tag.Timestamp);
Assert.True((DateTime.UtcNow - tag.Timestamp).Duration() < TimeSpan.FromMinutes(1));
}

[Fact]
public void Value_SetFalse_ShouldClearTargetBitOnly()
{
var (cbnt, tag) = Create(cacheOffset: 1, nthBit: 3);
cbnt.Cache.Span[1] = 0xFF;

tag.Value = false;

Assert.Equal(0b1111_0111, cbnt.Cache.Span[1]);
Assert.True(tag.IsDirty);
Assert.True(cbnt.IsDirty);
}

[Fact]
public void Value_SetTrue_ShouldNotTouchOtherBytes()
{
var (cbnt, tag) = Create(cacheOffset: 1, nthBit: 0);
cbnt.Cache.Span[0] = 0xAA;
cbnt.Cache.Span[2] = 0xBB;

tag.Value = true;

Assert.Equal(0xAA, cbnt.Cache.Span[0]);
Assert.Equal(0b0000_0001, cbnt.Cache.Span[1]);
Assert.Equal(0xBB, cbnt.Cache.Span[2]);
}

/// <summary>
/// 跨字节位(地址写成 DB200.101.9 之类)在工厂里会归一化成 NthBit=1、CacheOffset=TagOffset+1,
/// 赋值必须按 CacheOffset 写缓存,而不是按 TagOffset。
/// </summary>
[Fact]
public void Value_SetTrue_ShouldWriteAtCacheOffsetNotTagOffset()
{
var (cbnt, tag) = Create(tagOffset: 1, cacheOffset: 2, nthBit: 1);

tag.Value = true;

Assert.Equal(0, cbnt.Cache.Span[1]);
Assert.Equal(0b0000_0010, cbnt.Cache.Span[2]);
}

[Fact]
public void Value_Get_ShouldReadTargetBit()
{
var (cbnt, tag) = Create(cacheOffset: 1, nthBit: 3);
cbnt.Cache.Span[1] = 0b0000_1000;

Assert.True((bool)tag.Value!);

cbnt.Cache.Span[1] = 0b0000_0000;
Assert.False((bool)tag.Value!);
}

[Fact]
public void Value_SetNonBool_ShouldThrowArgumentException()
{
var (cbnt, tag) = Create(cacheOffset: 1, nthBit: 3);

var ex = Assert.Throws<ArgumentException>(() => tag.Value = 1);

Assert.Equal("value", ex.ParamName);
Assert.False(tag.IsDirty);
Assert.False(cbnt.IsDirty);
Assert.Equal(0, cbnt.Cache.Span[1]);
}

[Fact]
public void Value_SetNull_ShouldThrowArgumentException()
{
var (_, tag) = Create(cacheOffset: 1, nthBit: 3);

var ex = Assert.Throws<ArgumentException>(() => tag.Value = null);

Assert.Equal("value", ex.ParamName);
}
}
160 changes: 160 additions & 0 deletions tests/StdUnit.Tags.Tests/S7Tags/S7TagFactoryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
using StdUnit.Tags.S7;
using System;
using System.Xml.Linq;
using Xunit;

namespace StdUnit.Tags.Tests.S7Tags;

/// <summary>
/// <see cref="S7TagFactory"/>:测点种类分派、TagSize 归一化、位地址跨字节归一化、STR 的 maxlen 处理。
/// </summary>
public class S7TagFactoryTests
{
private static S7TagFactory CreateFactory(string startAddress = "DB200.100")
{
var builder = new S7TagCbntBuilder();
builder.WithCbntDescriptor(new TagCbntDescriptor { Name = "cbnt1", StartAddress = startAddress });
return builder.MakeS7TagFactory();
}

private static TagDescriptor CreateDescriptor(string kind, string rawAddress, int tagSize = 0, string tagName = "tag")
=> new TagDescriptor
{
TagName = tagName,
TagKind = kind,
RawAddress = rawAddress,
TagSize = tagSize,
};

[Theory]
[InlineData(BuiltinTagKinds.BIT, typeof(S7BitTagCbntor), 1, "DB200.104.1")]
[InlineData(BuiltinTagKinds.BYTE, typeof(S7ByteTagCbntor), 1, "DB200.104")]
[InlineData(BuiltinTagKinds.INT16, typeof(S7Int16TagCbntor), 2, "DB200.104")]
[InlineData(BuiltinTagKinds.UINT16, typeof(S7UInt16TagCbntor), 2, "DB200.104")]
[InlineData(BuiltinTagKinds.INT32, typeof(S7Int32TagCbntor), 4, "DB200.104")]
[InlineData(BuiltinTagKinds.UINT32, typeof(S7UInt32TagCbntor), 4, "DB200.104")]
[InlineData(BuiltinTagKinds.INT64, typeof(S7Int64TagCbntor), 8, "DB200.104")]
[InlineData(BuiltinTagKinds.UINT64, typeof(S7UInt64TagCbntor), 8, "DB200.104")]
[InlineData(BuiltinTagKinds.FLOAT, typeof(S7FloatTagCbntor), 4, "DB200.104")]
public void CreateTag_WhenTagSizeIsZero_ShouldCreateExpectedCbntorAndNormalizeTagSize(
string kind, Type expectedType, int expectedTagSize, string rawAddress)
{
var factory = CreateFactory();
var descriptor = CreateDescriptor(kind, rawAddress);

var tag = factory.CreateTag(descriptor);

Assert.Equal(expectedType, tag.GetType());
Assert.Equal(expectedTagSize, descriptor.TagSize);
Assert.Equal(expectedTagSize, tag.TagSize());
Assert.Equal(4, tag.TagOffset);
}

[Theory]
[InlineData(BuiltinTagKinds.BIT, "DB200.104.1")]
[InlineData(BuiltinTagKinds.BYTE, "DB200.104")]
[InlineData(BuiltinTagKinds.INT16, "DB200.104")]
[InlineData(BuiltinTagKinds.UINT16, "DB200.104")]
[InlineData(BuiltinTagKinds.INT32, "DB200.104")]
[InlineData(BuiltinTagKinds.UINT32, "DB200.104")]
[InlineData(BuiltinTagKinds.INT64, "DB200.104")]
[InlineData(BuiltinTagKinds.UINT64, "DB200.104")]
[InlineData(BuiltinTagKinds.FLOAT, "DB200.104")]
public void CreateTag_WhenTagSizeIsExplicit_ShouldKeepIt(string kind, string rawAddress)
{
var factory = CreateFactory();
var descriptor = CreateDescriptor(kind, rawAddress, tagSize: 16);

factory.CreateTag(descriptor);

Assert.Equal(16, descriptor.TagSize);
}

[Theory]
[InlineData("DB200.104.0", 4, 4, 0)]
[InlineData("DB200.104.7", 4, 4, 7)]
[InlineData("DB200.104.8", 4, 5, 0)]
[InlineData("DB200.104.9", 4, 5, 1)]
[InlineData("DB200.104.15", 4, 5, 7)]
public void CreateTag_Bit_ShouldNormalizeNthBitAcrossBytes(
string rawAddress, int expectedTagOffset, int expectedCacheOffset, byte expectedNthBit)
{
var factory = CreateFactory();
var descriptor = CreateDescriptor(BuiltinTagKinds.BIT, rawAddress);

var tag = Assert.IsType<S7BitTagCbntor>(factory.CreateTag(descriptor));

Assert.Equal(expectedTagOffset, tag.TagOffset);
Assert.Equal(expectedCacheOffset, tag.CacheOffset);
Assert.Equal(expectedNthBit, tag.NthBit);
Assert.Equal(1, descriptor.TagSize);
}

[Fact]
public void CreateTag_ShouldCalculateOffsetFromCbntStartAddress()
{
var factory = CreateFactory("DB200.100");
var descriptor = CreateDescriptor(BuiltinTagKinds.INT32, "DB200.132");

var tag = factory.CreateTag(descriptor);

Assert.Equal(32, tag.TagOffset);
}

[Fact]
public void CreateTag_Str_ShouldCalculateSizeFromMaxlen()
{
var factory = CreateFactory();
var descriptor = CreateDescriptor(BuiltinTagKinds.STR, "DB200.110", tagName: "str-tag");
descriptor.Extras["maxlen"] = new XAttribute("maxlen", "10");

var tag = Assert.IsType<S7StrTagCbntor>(factory.CreateTag(descriptor));

Assert.Equal(10, tag.Maxlen);
Assert.Equal(12, descriptor.TagSize);
Assert.Equal(10, tag.TagOffset);
}

[Fact]
public void CreateTag_Str_WhenMaxlenIsMissing_ShouldThrowTagsProjectXmlException()
{
var factory = CreateFactory();
var descriptor = CreateDescriptor(BuiltinTagKinds.STR, "DB200.110", tagName: "str-tag");

var ex = Assert.Throws<TagsProjectXmlException>(() => factory.CreateTag(descriptor));

Assert.Contains("maxlen", ex.Message);
Assert.Contains("Tag(str-tag)", ex.Location);
}

[Theory]
[InlineData("abc")]
[InlineData("0")]
public void CreateTag_Str_WhenMaxlenIsInvalid_ShouldThrowTagsProjectXmlException(string maxlen)
{
var factory = CreateFactory();
var descriptor = CreateDescriptor(BuiltinTagKinds.STR, "DB200.110", tagName: "str-tag");
descriptor.Extras["maxlen"] = new XAttribute("maxlen", maxlen);

var ex = Assert.Throws<TagsProjectXmlException>(() => factory.CreateTag(descriptor));

Assert.Contains("maxlen", ex.Message);
Assert.Contains("Tag(str-tag)", ex.Location);
}

[Theory]
[InlineData(BuiltinTagKinds.DOUBLE)]
[InlineData(BuiltinTagKinds.DI)]
[InlineData(BuiltinTagKinds.DO)]
[InlineData(BuiltinTagKinds.Unknown)]
public void CreateTag_WhenTagKindIsUnsupported_ShouldThrowTagsProjectConfigurationException(string kind)
{
var factory = CreateFactory();
var descriptor = CreateDescriptor(kind, "DB200.104", tagName: "bad-tag");

var ex = Assert.Throws<TagsProjectConfigurationException>(() => factory.CreateTag(descriptor));

Assert.Contains(kind, ex.Message);
Assert.Contains("Tag(bad-tag)", ex.Location);
}
}
Loading