From 1453bf28796c14e625764bed6d77175c74492564 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 12 Nov 2026 23:59:46 -0500 Subject: [PATCH] test(heidenhain): live gate for the derived Family / state-name / tool-axis leaves (#829) Passed on all four real iTNC 530 controls (Hermle49/59/70/29): Family ITnc530, ExecutionStateName Automatic, ProgramStateName Idle, Tool/AxisName Z (and BadNotSupported alongside Tool/AxisCode on Hermle49, which refuses R_RI 51). Claude-Session: https://claude.ai/code/session_01GT67AD99zQQqpLCVm3p7xk --- .../HeidenhainLiveIntegrationTests.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Heidenhain.Tests/HeidenhainLiveIntegrationTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Heidenhain.Tests/HeidenhainLiveIntegrationTests.cs index 4ddff1680..46de2e751 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Heidenhain.Tests/HeidenhainLiveIntegrationTests.cs +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Heidenhain.Tests/HeidenhainLiveIntegrationTests.cs @@ -86,6 +86,49 @@ public sealed class HeidenhainLiveIntegrationTests values[0].StatusCode.ShouldBe(HeidenhainStatus.Good); } + /// + /// Derived String leaves (family classification, pyLSV2 state names, tool axis name) — no extra wire request; + /// each is computed from a reply the session already reads. + /// + [Fact] + public async Task Real_control_derived_name_leaves() + { + Assert.SkipWhen(string.IsNullOrWhiteSpace(Host), "HEIDENHAIN_LSV2_HOST not set — real-controller gate skipped."); + var ct = TestContext.Current.CancellationToken; + var tags = new[] + { + TestTags.Raw("Controller/Model"), TestTags.Raw("Controller/Family"), + TestTags.Raw("Status/ExecutionState"), TestTags.Raw("Status/ExecutionStateName"), + TestTags.Raw("Status/ProgramState"), TestTags.Raw("Status/ProgramStateName"), + TestTags.Raw("Tool/AxisCode"), TestTags.Raw("Tool/AxisName"), + }; + var options = new HeidenhainDriverOptions + { + Devices = [new HeidenhainDeviceOptions { Host = Host!, Port = Port, DeviceName = "dev1" }], + RawTags = tags, + Access = new HeidenhainAccessOptions { InspectPassword = Environment.GetEnvironmentVariable("OTOPCUA_HEIDENHAIN_INSPECT_PASSWORD") is { Length: > 0 } ? "env:OTOPCUA_HEIDENHAIN_INSPECT_PASSWORD" : null }, + }; + await using var driver = new HeidenhainDriver(options, "live"); + await driver.InitializeAsync("{}", ct); + await driver.SubscribeAsync(tags.Select(t => t.RawPath).ToList(), TimeSpan.FromSeconds(1), ct); + var deadline = DateTime.UtcNow.AddSeconds(30); + IReadOnlyList values; + do + { + await Task.Delay(500, ct); + values = await driver.ReadAsync(tags.Select(t => t.RawPath).ToList(), ct); + } while ((values[1].StatusCode != HeidenhainStatus.Good || values[3].StatusCode != HeidenhainStatus.Good) && DateTime.UtcNow < deadline); + + for (var i = 0; i < tags.Length; i++) + TestContext.Current.SendDiagnosticMessage($"{tags[i].RawPath} = {values[i].Value} (0x{values[i].StatusCode:X8})"); + values[1].StatusCode.ShouldBe(HeidenhainStatus.Good); + values[1].Value.ShouldNotBe("Unknown"); + values[3].StatusCode.ShouldBe(HeidenhainStatus.Good); + values[5].StatusCode.ShouldBe(HeidenhainStatus.Good); + // A name leaf never disagrees with its raw leaf's quality. + values[7].StatusCode.ShouldBe(values[6].StatusCode); + } + /// #867: integer tool leaves must reach clients with their declared runtime type, not Double. [Fact] public async Task Real_control_tool_leaves_keep_integer_types()