mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-26 04:19:53 +08:00
# Description Regression fence for the NodeSet2 import + code-generation surface. This adds a single, purpose-authored "kitchen-sink" NodeSet2 (`Opc.Ua.CoverageTest.NodeSet2.xml`) plus a small dependent secondary model that deliberately flip a broad set of the knobs defined by `UANodeSet.xsd` (every node class, every `Definition` shape, custom reference types, all five modelling rules, scalar/array/matrix values, a deep branching instance tree, and a wide set of reference relations). The same bytes are then loaded into **real** servers (`ServerFixture<T>` + `ReferenceServer`) through three independent pipelines and validated node-by-node, attribute-by-attribute, reference-by-reference: 1. **Source-generation, hand-written manager** - the XML is consumed at compile time via `<AdditionalFiles ModelSourceGeneratorPrefix="Opc.Ua.CoverageTest">`; the generated `AddOpcUaCoverageTest(context)` composes the predefined nodes inside a `CustomNodeManager2`, so every node materialises as its generated `NodeState` subclass. 2. **Runtime import** - the identical XML is imported at startup through `RuntimeNodeSetNodeManagerFactory` / `AddRuntimeNodeSet`. 3. **Source-generation, generated fluent manager** - a `[NodeManager]`-attributed partial makes the generator emit the whole `FluentNodeManagerBase` manager, factory and typed fluent-builder surface. A shared, data-driven `[TestCaseSource]` battery runs the identical assertions against all three servers, and a cross-pipeline equivalence fixture asserts the three address spaces agree. A dependent **second namespace** (`.../Secondary/`) exercises cross-namespace `HasTypeDefinition`, `HasSubtype` and `HasComponent` references in every pipeline. Authoring the model surfaced two latent source-generator bugs, both fixed here: - `NodeSetToModelDesign` crashed on a top-level `<ServerUris>` table (`StringTable.Append(null)` for the reserved local-server slot). Added a null guard plus a focused regression test (`NodesetServerUrisTests`). - `FluentBuilderGenerator` emitted colliding typed-builder accessors for any model with more than one structure encoding, because `DataTypeEncoding` objects hang off their `DataType` and surfaced as parent-less "root" instances sharing the reserved `DefaultBinary`/`DefaultXml`/`DefaultJson` symbolic names. They are encoding metadata, never fluent-wired, so they are now excluded from the typed builder. A few edges are intentionally not asserted for cross-pipeline equivalence because the pipelines legitimately differ (documented in `CoverageNodeSet/README.md`): type-dictionary / `DataTypeDescription` nodes (the generator regenerates schema rather than emitting them), a handful of ambiguous duplicate-hierarchical / View-`Organizes` edges, and runtime structure stand-ins (structure definitions are validated via the `DataTypeDefinition` round-trip instead). ## Related Issues - Fixes #4251 ## Checklist _Put an `x` in the boxes that apply. You can complete these step by step after opening the PR._ - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [x] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>
116 lines
7.2 KiB
Markdown
116 lines
7.2 KiB
Markdown
# Coverage NodeSet test harness (issue #4251)
|
|
|
|
This directory contains a purpose-authored **"kitchen-sink" NodeSet2**
|
|
(`Assets/Opc.Ua.CoverageTest.NodeSet2.xml`) plus a small dependent **secondary**
|
|
model (`Assets/Opc.Ua.CoverageTestSecondary.NodeSet2.xml`) that deliberately flips
|
|
a broad set of the knobs defined by `UANodeSet.xsd`, and a data-driven assertion
|
|
battery that runs the **same** checks against the model loaded through three
|
|
independent pipelines:
|
|
|
|
1. **Source-generation (hand-written manager)** — the XML is consumed at compile
|
|
time via `<AdditionalFiles ModelSourceGeneratorPrefix="Opc.Ua.CoverageTest">`
|
|
in `Opc.Ua.Server.Tests.csproj`. The generated `AddOpcUaCoverageTest(context)`
|
|
extension composes the predefined nodes inside the hand-written
|
|
`CoverageTestNodeManager : CustomNodeManager2` (`CoverageTestSourceGenServer`).
|
|
Every authored node materialises as its generated `NodeState` subclass.
|
|
2. **Runtime import** — the identical XML is embedded as a resource and imported
|
|
at startup through `RuntimeNodeSetNodeManagerFactory` / `AddRuntimeNodeSet`
|
|
(`CoverageTestRuntimeServer`).
|
|
3. **Source-generation (generated fluent manager)** — a `[NodeManager]`-attributed
|
|
partial (`CoverageTestFluentNodeManager`) makes the generator emit the entire
|
|
`FluentNodeManagerBase` manager, factory and typed fluent-builder surface
|
|
(`CoverageTestFluentGenServer`).
|
|
|
|
Each server is started for real (`ServerFixture<T>` + `ReferenceServer`), and the
|
|
assertion battery is executed once per server, then a cross-pipeline equivalence
|
|
fixture asserts the three address spaces agree.
|
|
|
|
## Files
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `Assets/Opc.Ua.CoverageTest.NodeSet2.xml` | The primary kitchen-sink model. |
|
|
| `Assets/Opc.Ua.CoverageTest.NodeSet2.csv` | Matching SymbolicName/Id/NodeClass table for byte-stable ids. |
|
|
| `Assets/Opc.Ua.CoverageTestSecondary.NodeSet2.xml` | A dependent second-namespace model (cross-namespace references). |
|
|
| `CoverageTestCatalogue.cs` | The expected node + reference catalogue (drives the data-driven tests). |
|
|
| `CoverageTestSourceGenServer.cs` | Source-generation host (`CoverageTestNodeManager`), owns both namespaces. |
|
|
| `CoverageTestRuntimeServer.cs` | Runtime-import host (two dependency-sorted sources). |
|
|
| `CoverageTestFluentNodeManager.cs` / `CoverageTestSecondaryFluentNodeManager.cs` | `[NodeManager]` partials for the generated fluent managers. |
|
|
| `CoverageTestFluentGenServer.cs` | Generated-fluent-manager host. |
|
|
| `CoverageNodeSetAssertionsBase.cs` | The shared, data-driven assertion battery (generic over the server type). |
|
|
| `CoverageTestAssertionsTests.cs` | Concrete fixtures binding the battery to each pipeline. |
|
|
| `CoverageTestEquivalenceTests.cs` | Cross-pipeline structural equivalence fixture (all three servers). |
|
|
|
|
## What is exercised
|
|
|
|
- Every node class (`UAReferenceType`, `UADataType`, `UAObjectType`,
|
|
`UAVariableType`, `UAObject`, `UAVariable`, `UAMethod`, `UAView`).
|
|
- Every `Definition` shape (Enumeration, OptionSet, Structure, Structure with an
|
|
optional field, Union, an abstract structure, non-default `Purpose`).
|
|
- Scalar, **array** and **matrix** variable value shapes.
|
|
- A deep, branching instance tree (depth 4, two branches) navigated child-by-child.
|
|
- **NamespaceMetadata publication** — the `NamespaceMetadata` Object exists for
|
|
both namespaces (all pipelines) and, for the two source-generated pipelines,
|
|
surfaces the model's `NamespaceVersion` (`1.2.3`) and `NamespacePublicationDate`
|
|
(`2024-06-01`). The runtime-import pipeline carries no `ModelDependency` assembly
|
|
attribute, so it only asserts the Object and its `NamespaceUri`.
|
|
- **Deterministic method call** — invoking the authored `AddNumbers` method with no
|
|
bound handler returns `BadNotImplemented` and yields the identical status on every
|
|
call.
|
|
- A **second (dependent) namespace** with cross-namespace `HasTypeDefinition`,
|
|
`HasSubtype` and `HasComponent` references, hosted in all three pipelines.
|
|
- Custom reference types: hierarchical/asymmetric (with `InverseName`),
|
|
symmetric, an abstract type with a concrete subtype, plus inverse authoring.
|
|
- Non-default common/instance/variable/method/view attributes, all five
|
|
`HasModellingRule` targets, and a broad set of reference relations
|
|
(`HasComponent`, `HasProperty`, `HasOrderedComponent`, `HasSubtype`,
|
|
`HasTypeDefinition`, `HasEncoding`, `HasEventSource`, `HasCondition`,
|
|
`HasInterface`, `GeneratesEvent`/`AlwaysGeneratesEvent`, `FromState`/`ToState`/
|
|
`HasCause`/`HasEffect`, `HasTrueSubState`/`HasFalseSubState`, and the custom
|
|
reference types).
|
|
|
|
## Known pipeline differences (intentionally not asserted for equivalence)
|
|
|
|
The reference catalogue deliberately excludes edges where the two pipelines
|
|
legitimately differ, so the equivalence fixture stays strict on everything else:
|
|
|
|
- **Type dictionaries / `DataTypeDescription` nodes** are not authored. The
|
|
source generator regenerates the binary/XML schema from the
|
|
`DataTypeDefinition`s rather than emitting `DataTypeDictionary` nodes into the
|
|
address space, so hand-authored dictionary nodes would diverge. As a
|
|
consequence the `HasDescription` and `HasDictionaryEntry` relations are not
|
|
covered here.
|
|
- **Duplicate hierarchical references** to the same child (for example a
|
|
`HasComponent` alongside a custom hierarchical reference, or `HasNotifier`
|
|
alongside `HasEventSource`) and a `View`'s `Organizes` edges to already-parented
|
|
nodes are represented differently by the two importers, so those specific
|
|
edges are not asserted for equivalence.
|
|
- **Runtime structure stand-ins** — the runtime complex-type loader registers
|
|
the enumeration and option-set stand-ins for this multi-type model but not the
|
|
structure stand-ins; the structure *definitions* are validated directly via
|
|
the `DataTypeDefinition` round-trip instead.
|
|
|
|
## Related generator and product fixes
|
|
|
|
Authoring this model surfaced two latent source-generator issues and one product
|
|
bug, all fixed:
|
|
|
|
- A top-level `<ServerUris>` table crashed `NodeSetToModelDesign`
|
|
(`StringTable.Append(null)` for the reserved local-server slot). The guard is in
|
|
`tools/Opc.Ua.SourceGeneration.Core/Schema/NodeSetToModelDesign.cs` and is
|
|
regression-tested by `NodesetServerUrisTests` in
|
|
`Opc.Ua.SourceGeneration.Tests`.
|
|
- A model with more than one structure (hence more than one `Default Binary`
|
|
encoding) made the generated **typed fluent builder** emit colliding accessors,
|
|
because `DataTypeEncoding` objects hang off their `DataType` and surfaced as
|
|
parent-less "root" instances sharing the reserved `DefaultBinary`/`DefaultXml`/
|
|
`DefaultJson` symbolic names. `FluentBuilderGenerator.GetTopLevelInstances` now
|
|
skips `DataTypeEncoding` objects (they are encoding metadata, never fluent-wired).
|
|
- `NamespaceMetadataPublisher` never stamped a model's publication date because the
|
|
"unset" guard compared against `DateTime.MinValue` (`0001-01-01`) while an unset
|
|
`NamespacePublicationDate` defaults to the OPC UA epoch (`1601-01-01`); it now
|
|
uses `DateTimeUtc.IsNull`. It also only discovered the `ModelDependency` attribute
|
|
through a node manager's sync facade, which is a framework wrapper for
|
|
async-native (fluent) managers; it now scans both the async manager's and its sync
|
|
facade's assemblies so the fluent pipeline publishes version metadata too.
|