perf(mtconnect): sample chunks parse from bytes, no per-chunk string copies
This commit is contained in:
@@ -294,6 +294,113 @@ public sealed class MTConnectStreamsParseTests
|
||||
fromStream.Observations.ShouldBe(fromString.Observations);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------- the byte overload the /sample pump feeds on
|
||||
|
||||
[Theory]
|
||||
[InlineData(CurrentFixture)]
|
||||
[InlineData(SampleFixture)]
|
||||
[InlineData(GapFixture)]
|
||||
[InlineData(UnavailableFixture)]
|
||||
public void Parse_from_bytes_matches_the_string_overload_on_every_fixture(string fixture)
|
||||
{
|
||||
// The /sample pump hands the parser the chunk's raw bytes; a chunk parsed from bytes must be
|
||||
// indistinguishable from the same chunk round-tripped through a string.
|
||||
var fromBytes = MTConnectStreamsParser.Parse(File.ReadAllBytes(fixture));
|
||||
|
||||
var fromString = MTConnectStreamsParser.Parse(File.ReadAllText(fixture));
|
||||
fromBytes.InstanceId.ShouldBe(fromString.InstanceId);
|
||||
fromBytes.FirstSequence.ShouldBe(fromString.FirstSequence);
|
||||
fromBytes.NextSequence.ShouldBe(fromString.NextSequence);
|
||||
fromBytes.Observations.ShouldBe(fromString.Observations);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("\r\n")]
|
||||
[InlineData("\n")]
|
||||
[InlineData(" \t")]
|
||||
[InlineData("\r\n\r\n")]
|
||||
public void Parse_from_bytes_tolerates_framing_whitespace_AHEAD_of_the_xml_declaration(string residue)
|
||||
{
|
||||
// THE load-bearing leg of the byte path's trim, and the one that is not falsifiable by
|
||||
// accident: every fixture opens with an <?xml ...?> declaration, and XmlReader rejects even
|
||||
// one space ahead of one ("no whitespace characters are allowed to appear before it"). The
|
||||
// string overload trimmed it; a byte path that fed the frame straight to XDocument.Load
|
||||
// would turn an Agent that writes a blank line after the part headers into a hard parse
|
||||
// failure on every chunk. (Trailing whitespace and a leading BOM, by contrast, XmlReader
|
||||
// handles natively — the cases below pin that rather than the trim.)
|
||||
var bytes = Encoding.UTF8.GetBytes(residue).Concat(File.ReadAllBytes(SampleFixture)).ToArray();
|
||||
|
||||
var withResidue = MTConnectStreamsParser.Parse(bytes);
|
||||
|
||||
withResidue.NextSequence.ShouldBe(113);
|
||||
withResidue.Observations.ShouldBe(MTConnectStreamsParser.Parse(File.ReadAllText(SampleFixture)).Observations);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(CurrentFixture)]
|
||||
[InlineData(SampleFixture)]
|
||||
public void Parse_from_bytes_tolerates_a_utf8_byte_order_mark_ahead_of_the_declaration(string fixture)
|
||||
{
|
||||
// A chunk lifted out of a multipart frame can carry a BOM; the string path trimmed ''
|
||||
// for exactly this reason, so the byte path must handle the EF BB BF that decodes to it.
|
||||
var bytes = new byte[] { 0xEF, 0xBB, 0xBF }.Concat(File.ReadAllBytes(fixture)).ToArray();
|
||||
|
||||
var withBom = MTConnectStreamsParser.Parse(bytes);
|
||||
|
||||
withBom.Observations.ShouldBe(MTConnectStreamsParser.Parse(File.ReadAllText(fixture)).Observations);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("\r\n")]
|
||||
[InlineData("\r\n\r\n")]
|
||||
[InlineData("\n")]
|
||||
[InlineData(" \t\r\n")]
|
||||
public void Parse_from_bytes_tolerates_the_multipart_frames_trailing_line_break(string residue)
|
||||
{
|
||||
// The other half of what the string path's Trim covered: the framing's trailing line break.
|
||||
var bytes = File.ReadAllBytes(SampleFixture).Concat(Encoding.UTF8.GetBytes(residue)).ToArray();
|
||||
|
||||
var withResidue = MTConnectStreamsParser.Parse(bytes);
|
||||
|
||||
withResidue.NextSequence.ShouldBe(113);
|
||||
withResidue.Observations.ShouldBe(MTConnectStreamsParser.Parse(File.ReadAllText(SampleFixture)).Observations);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_from_bytes_tolerates_a_bom_behind_leading_whitespace()
|
||||
{
|
||||
// Whitespace THEN a BOM defeats XmlReader's encoding sniffing outright ("Data at the root
|
||||
// level is invalid"), so the trim has to interleave the two rather than strip a BOM only at
|
||||
// offset 0 — which is exactly what the string overload's Trim('', ' ', …) set did.
|
||||
var bytes = " \r\n"u8.ToArray()
|
||||
.Concat(new byte[] { 0xEF, 0xBB, 0xBF })
|
||||
.Concat(File.ReadAllBytes(CurrentFixture))
|
||||
.Concat("\r\n"u8.ToArray())
|
||||
.ToArray();
|
||||
|
||||
MTConnectStreamsParser.Parse(bytes).Observations
|
||||
.ShouldBe(MTConnectStreamsParser.Parse(File.ReadAllText(CurrentFixture)).Observations);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new byte[0])]
|
||||
[InlineData(new byte[] { (byte)' ', (byte)'\r', (byte)'\n' })]
|
||||
[InlineData(new byte[] { 0xEF, 0xBB, 0xBF })]
|
||||
public void Parse_from_bytes_throws_on_an_empty_payload(byte[] bytes)
|
||||
{
|
||||
// Same failure family as the string path: never a silently empty result.
|
||||
var ex = Should.Throw<InvalidDataException>(() => MTConnectStreamsParser.Parse(bytes));
|
||||
|
||||
ex.Message.ShouldContain("empty");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_from_bytes_throws_on_malformed_xml()
|
||||
{
|
||||
Should.Throw<InvalidDataException>(
|
||||
() => MTConnectStreamsParser.Parse("not xml at all"u8.ToArray()));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------- fails loudly, never silently empty
|
||||
|
||||
[Theory]
|
||||
|
||||
Reference in New Issue
Block a user