/* ======================================================================== * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; namespace Opc.Ua.SourceGeneration.Generator.Tests { /// /// Checks that the binary (.bsd) and XML (.xsd) schemas generated for a /// ModelDesign describe what the generated Encode methods write. /// [TestFixture] [Category("Generator")] [SetCulture("en-us")] [SetUICulture("en-us")] [Parallelizable] public class SchemaModelTests { [OneTimeSetUp] public void OneTimeSetUp() { Dictionary generated = DataTypeModelTests.Generate(Model); m_bsd = generated .Single(f => f.Key.EndsWith(".Types.bsd", StringComparison.Ordinal)) .Value; m_xsd = generated .Single(f => f.Key.EndsWith(".Types.xsd", StringComparison.Ordinal)) .Value; m_code = string.Concat(generated .Where(f => f.Key.EndsWith(".cs", StringComparison.Ordinal)) .Select(f => f.Value)); } /// /// D-12: a subtype of the OptionSet structure is encoded as the /// structure (Value and ValidBits ByteStrings), not as an integer. /// [Test] public void StructureOptionSetIsDescribedAsStructuredType() { string caps = Element(m_bsd, "opc:StructuredType", "Caps"); Assert.That(caps, Does.Contain("BaseType=\"ua:OptionSet\"")); Assert.That(caps, Does.Contain("Name=\"Value\" TypeName=\"opc:ByteString\"")); Assert.That(caps, Does.Contain("Name=\"ValidBits\" TypeName=\"opc:ByteString\"")); Assert.That(m_bsd, Does.Not.Contain(" /// D-12: the size of an integer OptionSet derived from another /// OptionSet is the size of the integer at the root, and a declared /// zero member is not duplicated by the synthetic "None". /// [Test] public void IntegerOptionSetLengthAndNone() { string wide = Element(m_bsd, "opc:EnumeratedType", "Wide"); Assert.That(wide, Does.Contain("LengthInBits=\"16\"")); string withZero = Element(m_bsd, "opc:EnumeratedType", "WithZero"); Assert.That( withZero.Split(["Value=\"0\""], StringSplitOptions.None), Has.Length.EqualTo(2)); Assert.That(withZero, Does.Not.Contain("Name=\"None\"")); } /// /// D-13: fields the generated code writes as a Variant are described /// as a Variant, an array as a counted array. /// [Test] public void NonArrayValueRanksAreDescribedAsWritten() { string ranks = Element(m_bsd, "opc:StructuredType", "Ranks"); Assert.That(ranks, Does.Contain("")); Assert.That(ranks, Does.Not.Contain("NoOfLoose")); Assert.That(ranks, Does.Contain("")); } /// /// D-5: a matrix field is an inline matrix whose value count is the /// product of the dimensions, which a DataTypeDictionary cannot /// describe: "any Structure with a field or nested field mapped to an /// inline matrix ... shall not be included in a DataTypeDictionary" /// (OPC 10000-6 5.2.5). It stays in the XML schema, which has the /// Matrix type. A field allowing subtypes is an ExtensionObject and /// does not nest the matrix. /// [Test] public void StructuresWithInlineMatricesAreNotInTheBinarySchema() { Assert.That(m_bsd, Does.Not.Contain("")); Assert.That(m_xsd, Does.Contain(" /// D-14: the XmlEncoder writes an EncodingMask element first for a /// structure with optional fields; a derived type introducing /// optional fields writes it ahead of the base fields and is /// flattened; a union may carry no member at all. /// [Test] public void XmlSchemaDescribesEncodingMaskAndEmptyUnion() { string opt = Element(m_xsd, "xs:complexType", "Opt"); Assert.That( opt.IndexOf("name=\"EncodingMask\"", StringComparison.Ordinal), Is.GreaterThan(0).And.LessThan(opt.IndexOf("name=\"Note\"", StringComparison.Ordinal))); // OPC 10000-6 5.3.6: the mask element is mandatory and typed as in // the example of the clause. Assert.That(opt, Does.Contain("")); string derived = Element(m_xsd, "xs:complexType", "OptDerived"); Assert.That(derived, Does.Not.Contain("xs:extension")); int mask = derived.IndexOf("name=\"EncodingMask\"", StringComparison.Ordinal); int a = derived.IndexOf("name=\"A\"", StringComparison.Ordinal); int b = derived.IndexOf("name=\"B\"", StringComparison.Ordinal); Assert.That(mask, Is.GreaterThan(0)); Assert.That(a, Is.GreaterThan(mask)); Assert.That(b, Is.GreaterThan(a)); // The mask is written once, by the type that introduced it. string leaf = Element(m_xsd, "xs:complexType", "OptLeaf"); Assert.That(leaf, Does.Contain("xs:extension")); Assert.That(leaf, Does.Not.Contain("EncodingMask")); string choice = Element(m_xsd, "xs:complexType", "Choice"); Assert.That(choice, Does.Contain("")); } /// /// D5: only a Structure field allowing subtypes is an ExtensionObject /// (OPC 10000-6 5.1.7); an abstract non-structure field allowing /// subtypes is written as a Variant and described as one. /// [Test] public void SubtypedNonStructureFieldsAreVariants() { string subtyped = Element(m_bsd, "opc:StructuredType", "Subtyped"); Assert.That(subtyped, Does.Contain("")); Assert.That(subtyped, Does.Contain("")); Assert.That(subtyped, Does.Contain("")); } /// /// D5: an EnumeratedValue Value is an xs:int (OPC 10000-5 C.2.7), so /// the mask of bit 31 or above is not written as a value. /// [Test] public void OptionSetMasksBeyondXsIntAreLeftOut() { string bits = Element(m_bsd, "opc:EnumeratedType", "HighBits"); Assert.That(bits, Does.Contain("")); Assert.That(bits, Does.Not.Contain("Value=\"2147483648\"")); Assert.That(bits, Does.Not.Contain("Name=\"Top\"")); Assert.That(bits, Does.Contain("