diff --git a/Common.Testing/Common.Testing.csproj b/Common.Testing/Common.Testing.csproj
new file mode 100644
index 00000000..9f3dffe6
--- /dev/null
+++ b/Common.Testing/Common.Testing.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net472
+ Skyline.DataMiner.CICD.Validators.Common.Testing
+
+
+
+
+
+
+
+
+ ..\DLLs\QActionHelper.dll
+
+
+
+
diff --git a/Common.Testing/ProtocolQActionHelperProvider.cs b/Common.Testing/ProtocolQActionHelperProvider.cs
new file mode 100644
index 00000000..83bf33ba
--- /dev/null
+++ b/Common.Testing/ProtocolQActionHelperProvider.cs
@@ -0,0 +1,71 @@
+namespace SLDisUnitTestsShared
+{
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Text;
+ using System.Threading;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Interfaces;
+ using Skyline.DataMiner.Scripting;
+
+ public class ProtocolQActionHelperProvider : IProtocolQActionHelperProvider
+ {
+ private const string AutoGeneratedCodeByDis = "// This is auto-generated code by DIS. Do not modify.";
+ private static readonly Mutex _mutex;
+
+ static ProtocolQActionHelperProvider()
+ {
+ string mutexId = $"Global\\{typeof(ProtocolQActionHelperProvider).GUID}";
+ _mutex = new Mutex(false, mutexId);
+ }
+
+ public string GetProtocolQActionHelper(string protocolCode, bool ignoreErrors = false)
+ {
+ var tempPath = Path.GetTempPath();
+ var tempName = Guid.NewGuid().ToString("N");
+ var tempFile = Path.Combine(tempPath, tempName + ".txt");
+
+ List result;
+
+ // prevent System.IO.IOException: The process cannot access the file 'C:\Skyline DataMiner\logging\SLQActionHelper.txt'
+ try
+ {
+ _mutex.WaitOne();
+ result = QActionHelper.CreateProtocolQActionHelperFromString(protocolCode, tempPath, tempName);
+ }
+ finally
+ {
+ _mutex.ReleaseMutex();
+ }
+
+ if (!ignoreErrors && result != null && result.Count > 0)
+ {
+ StringBuilder error = new StringBuilder();
+ error.Append("Couldn't generate QAction helper:");
+
+ foreach (var err in result)
+ {
+ error.Append("\n Line " + err.Line + ": " + err.Description);
+ }
+
+ throw new Exception(error.ToString());
+ }
+
+ var csContent = new StringBuilder();
+ csContent.AppendLine(AutoGeneratedCodeByDis);
+
+ if (File.Exists(tempFile))
+ {
+ csContent.Append(File.ReadAllText(tempFile));
+ File.Delete(tempFile);
+ }
+ else
+ {
+ csContent.Append("namespace Skyline.DataMiner.Scripting { public class SLProtocolExt : SLProtocol { } }");
+ }
+
+ return csContent.ToString();
+ }
+ }
+}
diff --git a/Common.Testing/ProtocolTestsHelper.cs b/Common.Testing/ProtocolTestsHelper.cs
new file mode 100644
index 00000000..cee25838
--- /dev/null
+++ b/Common.Testing/ProtocolTestsHelper.cs
@@ -0,0 +1,80 @@
+namespace SLDisUnitTestsShared
+{
+ using System.IO;
+ using System.Reflection;
+ using System.Runtime.CompilerServices;
+ using System.Text;
+
+ using Skyline.DataMiner.CICD.Models.Common;
+ using Skyline.DataMiner.CICD.Models.Protocol;
+ using Skyline.DataMiner.CICD.Models.Protocol.Read;
+ using Skyline.DataMiner.CICD.Models.Protocol.Read.Interfaces;
+ using Skyline.DataMiner.CICD.Parsers.Common.Xml;
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+ using Skyline.DataMiner.CICD.Validators.Common.Interfaces;
+ using EditModel = Skyline.DataMiner.CICD.Models.Protocol.Edit;
+ using EditXml = Skyline.DataMiner.CICD.Parsers.Common.XmlEdit;
+
+ public static class ProtocolTestsHelper
+ {
+ public static (IProtocolModel model, XmlDocument document, string protocolCode) ReadProtocol(string fileName, [CallerFilePath] string pathToClassFile = "")
+ {
+ string filePath = Path.Combine(Path.GetDirectoryName(pathToClassFile), fileName);
+
+ string code;
+ var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
+ using (var textReader = new StreamReader(fileStream))
+ {
+ code = textReader.ReadToEnd();
+ }
+
+ return ParseProtocol(code);
+ }
+
+ public static (IProtocolModel model, XmlDocument document, string protocolCode) ParseProtocol(string protocolCode)
+ {
+ Parser parser = new Parser(new StringBuilder(protocolCode));
+
+ return (new ProtocolModel(parser.Document), parser.Document, protocolCode);
+ }
+
+ public static QActionCompilationModel GetQActionCompilationModel(string xmlCode)
+ {
+ var document = new Parser(xmlCode).Document;
+ var model = new ProtocolModel(document);
+
+ return GetQActionCompilationModel(model, xmlCode);
+ }
+
+ public static QActionCompilationModel GetQActionCompilationModel(IProtocolModel model, string xmlCode)
+ {
+ var baseDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
+ baseDir = Path.GetFullPath(Path.Combine(baseDir, @"..\..\..\..\DLLs"));
+
+ var dllImportResolver = new InternalFilesAssemblyResolver(baseDir);
+ var qactionHelperProvider = new ProtocolQActionHelperProvider();
+
+ string qactionHelperSourceCode = qactionHelperProvider.GetProtocolQActionHelper(xmlCode, ignoreErrors: true);
+ return new QActionCompilationModel(qactionHelperSourceCode, model, dllImportResolver);
+ }
+
+ public static IProtocolInputData GetProtocolInputData(string fileName, [CallerFilePath] string pathToClassFile = "")
+ {
+ (IProtocolModel model, XmlDocument document, string protocolCode) = ReadProtocol(fileName, pathToClassFile);
+
+ var qactionCompilationModel = GetQActionCompilationModel(model, protocolCode);
+
+ return new ProtocolInputData(model, document, qactionCompilationModel);
+ }
+
+ public static EditModel.Protocol GetEditProtocol(IProtocolModel model)
+ {
+ return new EditModel.Protocol(model.Protocol);
+ }
+
+ public static EditModel.Protocol GetEditProtocol(IProtocolModel model, EditXml.XmlElement xmlElement)
+ {
+ return new EditModel.Protocol(model.Protocol, xmlElement);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/Common.csproj b/Common/Common.csproj
index 7ce05521..34c59171 100644
--- a/Common/Common.csproj
+++ b/Common/Common.csproj
@@ -18,7 +18,7 @@
-
+
@@ -35,6 +35,7 @@
+
diff --git a/Common/Model/ValidatorSettings.cs b/Common/Model/ValidatorSettings.cs
index 9c8991f6..28010fc9 100644
--- a/Common/Model/ValidatorSettings.cs
+++ b/Common/Model/ValidatorSettings.cs
@@ -1,6 +1,7 @@
namespace Skyline.DataMiner.CICD.Validators.Common.Model
{
- using System.Collections.Generic;
+ using System;
+ using System.Collections.Generic;
using Skyline.DataMiner.CICD.Common;
using Skyline.DataMiner.XmlSchemas.Protocol;
@@ -12,12 +13,25 @@ public class ValidatorSettings
{
private readonly List<(Category catergory, uint checkId)> testsToExecute;
+ ///
+ /// Initializes a new instance of the class.
+ /// Used for unit tests
+ ///
+ internal ValidatorSettings()
+ {
+ testsToExecute = new List<(Category catergory, uint checkId)>();
+ UnitList = new UnitList();
+ MinimumSupportedDataMinerVersion = new DataMinerVersion(new Version(10, 1, 0, 0), 9966);
+ }
+
///
/// Initializes a new instance of the class.
///
public ValidatorSettings(DataMinerVersion minimumSupportedDataMinerVersion)
{
testsToExecute = new List<(Category catergory, uint checkId)>();
+
+ // TODO-MOD: Probably don't initialize it here just in case an error happens? Maybe only on the get of the property if the field is null still?
UnitList = new UnitList();
MinimumSupportedDataMinerVersion = minimumSupportedDataMinerVersion;
diff --git a/CommonTests/CommonTests.csproj b/CommonTests/CommonTests.csproj
index 57ea5900..08f8c7e7 100644
--- a/CommonTests/CommonTests.csproj
+++ b/CommonTests/CommonTests.csproj
@@ -1,7 +1,7 @@
- net462;net6.0
+ net472;net6.0
disable
disable
diff --git a/DLLs/Interop.SLDms.dll b/DLLs/Interop.SLDms.dll
new file mode 100644
index 00000000..ba062a8b
Binary files /dev/null and b/DLLs/Interop.SLDms.dll differ
diff --git a/DLLs/Newtonsoft.Json.dll b/DLLs/Newtonsoft.Json.dll
new file mode 100644
index 00000000..7af125a2
Binary files /dev/null and b/DLLs/Newtonsoft.Json.dll differ
diff --git a/DLLs/QActionHelper.dll b/DLLs/QActionHelper.dll
new file mode 100644
index 00000000..6de74a3d
Binary files /dev/null and b/DLLs/QActionHelper.dll differ
diff --git a/DLLs/QActionHelperBaseClasses.dll b/DLLs/QActionHelperBaseClasses.dll
new file mode 100644
index 00000000..c35527b9
Binary files /dev/null and b/DLLs/QActionHelperBaseClasses.dll differ
diff --git a/DLLs/SLLoggerUtil.dll b/DLLs/SLLoggerUtil.dll
new file mode 100644
index 00000000..7c1d9a8a
Binary files /dev/null and b/DLLs/SLLoggerUtil.dll differ
diff --git a/DLLs/SLManagedAutomation.dll b/DLLs/SLManagedAutomation.dll
new file mode 100644
index 00000000..87d1efff
Binary files /dev/null and b/DLLs/SLManagedAutomation.dll differ
diff --git a/DLLs/SLManagedScripting.dll b/DLLs/SLManagedScripting.dll
new file mode 100644
index 00000000..85aaa085
Binary files /dev/null and b/DLLs/SLManagedScripting.dll differ
diff --git a/DLLs/SLNetTypes.dll b/DLLs/SLNetTypes.dll
new file mode 100644
index 00000000..e3a82189
Binary files /dev/null and b/DLLs/SLNetTypes.dll differ
diff --git a/DLLs/Skyline.DataMiner.Storage.Types.dll b/DLLs/Skyline.DataMiner.Storage.Types.dll
new file mode 100644
index 00000000..a400831a
Binary files /dev/null and b/DLLs/Skyline.DataMiner.Storage.Types.dll differ
diff --git a/DLLs/readme.txt b/DLLs/readme.txt
new file mode 100644
index 00000000..b7cf60dd
--- /dev/null
+++ b/DLLs/readme.txt
@@ -0,0 +1,4 @@
+This folder should contain files from latest main release:
+S:\Public\DataMiner Software\DataMiner Files\Release\
+
+These DLLs are only to be used for unit tests. They should never be included in the actual NuGet packages.
\ No newline at end of file
diff --git a/Protocol.Features/Protocol.Features.csproj b/Protocol.Features/Protocol.Features.csproj
index 16d1a720..e203c9ac 100644
--- a/Protocol.Features/Protocol.Features.csproj
+++ b/Protocol.Features/Protocol.Features.csproj
@@ -35,5 +35,9 @@
+
+
+
+
diff --git a/Protocol.FeaturesTests/Features/10.0/DashboardTests.cs b/Protocol.FeaturesTests/Features/10.0/DashboardTests.cs
new file mode 100644
index 00000000..61687ac9
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.0/DashboardTests.cs
@@ -0,0 +1,41 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ [TestClass]
+ public class DashboardTests
+ {
+ private static Dashboard check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new Dashboard();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed()
+ {
+ const string code = "";
+ var input = new ProtocolInputData(code);
+
+ FeatureCheckContext context = new FeatureCheckContext(input);
+
+ var result = check.CheckIfUsed(context);
+ var expected = context.Model.Protocol.Params.Select(x => new FeatureCheckResultItem(x));
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.0/DynamicParameterReplicationTests.cs b/Protocol.FeaturesTests/Features/10.0/DynamicParameterReplicationTests.cs
new file mode 100644
index 00000000..54b0f2b4
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.0/DynamicParameterReplicationTests.cs
@@ -0,0 +1,41 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ [TestClass]
+ public class DynamicParameterReplicationTests
+ {
+ private static DynamicParameterReplication check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new DynamicParameterReplication();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed()
+ {
+ const string code = "";
+ var input = new ProtocolInputData(code);
+
+ FeatureCheckContext context = new FeatureCheckContext(input);
+
+ var result = check.CheckIfUsed(context);
+ var expected = context.Model.Protocol.Params.Select(x => new FeatureCheckResultItem(x));
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.0/ExposerTests.cs b/Protocol.FeaturesTests/Features/10.0/ExposerTests.cs
new file mode 100644
index 00000000..309129b5
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.0/ExposerTests.cs
@@ -0,0 +1,41 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ [TestClass]
+ public class ExposerTests
+ {
+ private static Exposer check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new Exposer();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed()
+ {
+ const string code = " | ";
+
+ var input = new ProtocolInputData(code);
+ var context = new FeatureCheckContext(input);
+
+ var result = check.CheckIfUsed(context);
+ var expected = context.Model.Protocol.Topologies.Select(x => new FeatureCheckResultItem(x));
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.0/GetPkIdByTableIdTests.cs b/Protocol.FeaturesTests/Features/10.0/GetPkIdByTableIdTests.cs
new file mode 100644
index 00000000..34f44011
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.0/GetPkIdByTableIdTests.cs
@@ -0,0 +1,46 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.Models.Protocol.Read;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Interfaces;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+ using SLDisUnitTestsShared;
+
+ [TestClass]
+ public class GetPkIdByTableIdTests
+ {
+ private static GetPkIdByTableId check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new GetPkIdByTableId();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed_CSharp()
+ {
+ const string fileName = "GetPkIdByTableIdTests.xml";
+ var input = ProtocolTestsHelper.GetProtocolInputData(fileName);
+
+ FeatureCheckContext context = new FeatureCheckContext(input, false);
+
+ IFeatureCheckResult result = check.CheckIfUsed(context);
+
+ Assert.IsTrue(result.IsUsed);
+ Assert.IsTrue(result.FeatureItems.All(x => x is CSharpFeatureCheckResultItem));
+
+ var ids = result.FeatureItems.Select(item => ((IQActionsQAction)item.Node).Id.Value);
+ var expectedIds = context.Model.Protocol.QActions.Select(qaction => qaction.Id.Value);
+ ids.Should().BeEquivalentTo(expectedIds);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.0/GetPkIdByTableIdTests.xml b/Protocol.FeaturesTests/Features/10.0/GetPkIdByTableIdTests.xml
new file mode 100644
index 00000000..abe21b0f
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.0/GetPkIdByTableIdTests.xml
@@ -0,0 +1,32 @@
+
+ Testing
+ 1.0.0.1
+
+
+
+/// DataMiner QAction Class: Test.
+///
+public static class QAction
+{
+ ///
+ /// The QAction entry point.
+ ///
+ /// Link with SLProtocol process.
+ public static void Run(SLProtocol protocol)
+ {
+ try
+ {
+ protocol.NotifyProtocol(394, null, null);
+ }
+ catch (Exception ex)
+ {
+ protocol.Log("QA" + protocol.QActionID + "|" + protocol.GetTriggerParameter() + "|Run|Exception thrown:" + Environment.NewLine + ex, LogType.Error, LogLevel.NoLogging);
+ }
+ }
+}]]>
+
+
+
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.0/GetTableIdByColumnId.cs b/Protocol.FeaturesTests/Features/10.0/GetTableIdByColumnId.cs
new file mode 100644
index 00000000..6a235ef9
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.0/GetTableIdByColumnId.cs
@@ -0,0 +1,49 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Interfaces;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ using Skyline.DataMiner.CICD.Models.Protocol.Read;
+ using Skyline.DataMiner.CICD.Models.Protocol.Read.Interfaces;
+ using Skyline.DataMiner.CICD.Parsers.Common.Xml;
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+ using SLDisUnitTestsShared;
+
+ [TestClass]
+ public class GetTableIdByColumnIdTests
+ {
+ private static GetTableIdByColumnId check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new GetTableIdByColumnId();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed_CSharp()
+ {
+ const string fileName = "GetTableIdByColumnIdTests.xml";
+ var input = ProtocolTestsHelper.GetProtocolInputData(fileName);
+
+ FeatureCheckContext context = new FeatureCheckContext(input, false);
+
+ IFeatureCheckResult result = check.CheckIfUsed(context);
+
+ Assert.IsTrue(result.IsUsed);
+ Assert.IsTrue(result.FeatureItems.All(x => x is CSharpFeatureCheckResultItem));
+
+ var ids = result.FeatureItems.Select(item => ((IQActionsQAction)item.Node).Id.Value);
+ var expectedIds = context.Model.Protocol.QActions.Select(qaction => qaction.Id.Value);
+ ids.Should().BeEquivalentTo(expectedIds);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.0/GetTableIdByColumnIdTests.xml b/Protocol.FeaturesTests/Features/10.0/GetTableIdByColumnIdTests.xml
new file mode 100644
index 00000000..f46c9402
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.0/GetTableIdByColumnIdTests.xml
@@ -0,0 +1,32 @@
+
+ Testing
+ 1.0.0.1
+
+
+
+/// DataMiner QAction Class: Test.
+///
+public static class QAction
+{
+ ///
+ /// The QAction entry point.
+ ///
+ /// Link with SLProtocol process.
+ public static void Run(SLProtocol protocol)
+ {
+ try
+ {
+ protocol.NotifyProtocol(393, null, null);
+ }
+ catch (Exception ex)
+ {
+ protocol.Log("QA" + protocol.QActionID + "|" + protocol.GetTriggerParameter() + "|Run|Exception thrown:" + Environment.NewLine + ex, LogType.Error, LogLevel.NoLogging);
+ }
+ }
+}]]>
+
+
+
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.0/HistorySets_FillArrayNoDelete_BulkTests.cs b/Protocol.FeaturesTests/Features/10.0/HistorySets_FillArrayNoDelete_BulkTests.cs
new file mode 100644
index 00000000..a6e1d1f0
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.0/HistorySets_FillArrayNoDelete_BulkTests.cs
@@ -0,0 +1,47 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Interfaces;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ using Skyline.DataMiner.CICD.Models.Protocol.Read;
+
+ using SLDisUnitTestsShared;
+
+ [TestClass]
+ public class HistorySets_FillArrayNoDelete_BulkTests
+ {
+ private static HistorySets_FillArrayNoDelete_Bulk check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new HistorySets_FillArrayNoDelete_Bulk();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed_CSharp()
+ {
+ const string fileName = "HistorySets_FillArrayNoDelete_BulkTests.xml";
+ var input = ProtocolTestsHelper.GetProtocolInputData(fileName);
+
+ FeatureCheckContext context = new FeatureCheckContext(input, false);
+
+ IFeatureCheckResult result = check.CheckIfUsed(context);
+
+ Assert.IsTrue(result.IsUsed);
+ Assert.IsTrue(result.FeatureItems.All(x => x is CSharpFeatureCheckResultItem));
+
+ var ids = result.FeatureItems.Select(item => ((IQActionsQAction)item.Node).Id.Value);
+ var expectedIds = context.Model.Protocol.QActions.Select(qaction => qaction.Id.Value);
+ ids.Should().BeEquivalentTo(expectedIds);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.0/HistorySets_FillArrayNoDelete_BulkTests.xml b/Protocol.FeaturesTests/Features/10.0/HistorySets_FillArrayNoDelete_BulkTests.xml
new file mode 100644
index 00000000..6e0105ea
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.0/HistorySets_FillArrayNoDelete_BulkTests.xml
@@ -0,0 +1,152 @@
+
+ Testing
+ 1.0.0.1
+
+
+ TestTable
+ Test Table
+ array
+
+
+
+
+ Test Table
+
+
+ true
+
+
+ table
+
+
+
+ TestTable_Index
+ Index (Test Table)
+ read
+
+ This is the key used internally by DataMiner to identify the table entries.
+
+
+ other
+ string
+ next param
+
+
+ true
+
+
+ string
+
+
+
+
+
+
+/// DataMiner QAction Class: Test.
+///
+public static class QAction
+{
+ ///
+ /// The QAction entry point.
+ ///
+ /// Link with SLProtocol process.
+ public static void Run(SLProtocol protocol)
+ {
+ try
+ {
+ DateTime time = new DateTime(2021, 4, 8, 8, 5, 13);
+ protocol.NotifyProtocol(194, new object[] { 1234, true, time }, new object[0]);
+ }
+ catch (Exception ex)
+ {
+ protocol.Log("QA" + protocol.QActionID + "|" + protocol.GetTriggerParameter() + "|Run|Exception thrown:" + Environment.NewLine + ex, LogType.Error, LogLevel.NoLogging);
+ }
+ }
+}]]>
+
+
+
+/// DataMiner QAction Class: Test.
+///
+public static class QAction
+{
+ ///
+ /// The QAction entry point.
+ ///
+ /// Link with SLProtocol process.
+ public static void Run(SLProtocol protocol)
+ {
+ try
+ {
+ DateTime time = new DateTime(2021, 4, 8, 8, 5, 13);
+ protocol.FillArrayNoDelete(1234, new object[0], time);
+ }
+ catch (Exception ex)
+ {
+ protocol.Log("QA" + protocol.QActionID + "|" + protocol.GetTriggerParameter() + "|Run|Exception thrown:" + Environment.NewLine + ex, LogType.Error, LogLevel.NoLogging);
+ }
+ }
+}]]>
+
+
+
+/// DataMiner QAction Class: Test.
+///
+public static class QAction
+{
+ ///
+ /// The QAction entry point.
+ ///
+ /// Link with SLProtocol process.
+ public static void Run(SLProtocol protocol)
+ {
+ try
+ {
+ DateTime time = new DateTime(2021, 4, 8, 8, 5, 13);
+ new QActionTable(protocol, 1324, "").FillArrayNoDelete(new object[0], time);
+ }
+ catch (Exception ex)
+ {
+ protocol.Log("QA" + protocol.QActionID + "|" + protocol.GetTriggerParameter() + "|Run|Exception thrown:" + Environment.NewLine + ex, LogType.Error, LogLevel.NoLogging);
+ }
+ }
+}]]>
+
+
+
+/// DataMiner QAction Class: Test.
+///
+public static class QAction
+{
+ ///
+ /// The QAction entry point.
+ ///
+ /// Link with SLProtocol process.
+ public static void Run(SLProtocol protocol)
+ {
+ try
+ {
+ DateTime time = new DateTime(2021, 4, 8, 8, 5, 13);
+ new TesttableQActionTable(protocol, 1000, "").FillArrayNoDelete(new object[0], time);
+ }
+ catch (Exception ex)
+ {
+ protocol.Log("QA" + protocol.QActionID + "|" + protocol.GetTriggerParameter() + "|Run|Exception thrown:" + Environment.NewLine + ex, LogType.Error, LogLevel.NoLogging);
+ }
+ }
+}]]>
+
+
+
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.0/HistorySets_FillArrayWithColumn_BulkTests.cs b/Protocol.FeaturesTests/Features/10.0/HistorySets_FillArrayWithColumn_BulkTests.cs
new file mode 100644
index 00000000..fd0b9c1b
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.0/HistorySets_FillArrayWithColumn_BulkTests.cs
@@ -0,0 +1,47 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Interfaces;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ using Skyline.DataMiner.CICD.Models.Protocol.Read;
+
+ using SLDisUnitTestsShared;
+
+ [TestClass]
+ public class HistorySets_FillArrayWithColumn_BulkTests
+ {
+ private static HistorySets_FillArrayWithColumn_Bulk check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new HistorySets_FillArrayWithColumn_Bulk();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed_CSharp()
+ {
+ const string fileName = "HistorySets_FillArrayWithColumn_BulkTests.xml";
+ var input = ProtocolTestsHelper.GetProtocolInputData(fileName);
+
+ FeatureCheckContext context = new FeatureCheckContext(input, false);
+
+ IFeatureCheckResult result = check.CheckIfUsed(context);
+
+ Assert.IsTrue(result.IsUsed);
+ Assert.IsTrue(result.FeatureItems.All(x => x is CSharpFeatureCheckResultItem));
+
+ var ids = result.FeatureItems.Select(item => ((IQActionsQAction)item.Node).Id.Value);
+ var expectedIds = context.Model.Protocol.QActions.Select(qaction => qaction.Id.Value);
+ ids.Should().BeEquivalentTo(expectedIds);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.0/HistorySets_FillArrayWithColumn_BulkTests.xml b/Protocol.FeaturesTests/Features/10.0/HistorySets_FillArrayWithColumn_BulkTests.xml
new file mode 100644
index 00000000..ac141f85
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.0/HistorySets_FillArrayWithColumn_BulkTests.xml
@@ -0,0 +1,153 @@
+
+ Testing
+ 1.0.0.1
+
+
+ TestTable
+ Test Table
+ array
+
+
+
+
+ Test Table
+
+
+ true
+
+
+ table
+
+
+
+ TestTable_Index
+ Index (Test Table)
+ read
+
+ This is the key used internally by DataMiner to identify the table entries.
+
+
+ other
+ string
+ next param
+
+
+ true
+
+
+ string
+
+
+
+
+
+
+/// DataMiner QAction Class: Test.
+///
+public static class QAction
+{
+ ///
+ /// The QAction entry point.
+ ///
+ /// Link with SLProtocol process.
+ public static void Run(SLProtocol protocol)
+ {
+ try
+ {
+ DateTime time = new DateTime(2021, 4, 8, 8, 5, 13);
+ protocol.NotifyProtocol((int)SLNetMessages.NotifyType.NT_FILL_ARRAY_WITH_COLUMN, new object[] { 1000, 1, new object[] { true, time } }, new object[] { null, null });
+ }
+ catch (Exception ex)
+ {
+ protocol.Log("QA" + protocol.QActionID + "|" + protocol.GetTriggerParameter() + "|Run|Exception thrown:" + Environment.NewLine + ex, LogType.Error, LogLevel.NoLogging);
+ }
+ }
+}]]>
+
+
+
+/// DataMiner QAction Class: Test.
+///
+public static class QAction
+{
+ ///
+ /// The QAction entry point.
+ ///
+ /// Link with SLProtocol process.
+ public static void Run(SLProtocol protocol)
+ {
+ try
+ {
+ DateTime time = new DateTime(2021, 4, 8, 8, 5, 13);
+ protocol.FillArrayWithColumn(1234, 1, new object[0], new object[0], time);
+ }
+ catch (Exception ex)
+ {
+ protocol.Log("QA" + protocol.QActionID + "|" + protocol.GetTriggerParameter() + "|Run|Exception thrown:" + Environment.NewLine + ex, LogType.Error, LogLevel.NoLogging);
+ }
+ }
+}]]>
+
+
+
+/// DataMiner QAction Class: Test.
+///
+public static class QAction
+{
+ ///
+ /// The QAction entry point.
+ ///
+ /// Link with SLProtocol process.
+ public static void Run(SLProtocol protocol)
+ {
+ try
+ {
+ DateTime time = new DateTime(2021, 4, 8, 8, 5, 13);
+ new QActionTable(protocol, 1324, "").SetColumn(1, new string[0], new object[0], time);
+ }
+ catch (Exception ex)
+ {
+ protocol.Log("QA" + protocol.QActionID + "|" + protocol.GetTriggerParameter() + "|Run|Exception thrown:" + Environment.NewLine + ex, LogType.Error, LogLevel.NoLogging);
+ }
+ }
+}]]>
+
+
+
+/// DataMiner QAction Class: Test.
+///
+public static class QAction
+{
+ ///
+ /// The QAction entry point.
+ ///
+ /// Link with SLProtocol process.
+ public static void Run(SLProtocol protocol)
+ {
+ try
+ {
+ DateTime time = new DateTime(2021, 4, 8, 8, 5, 13);
+ new TesttableQActionTable(protocol, 1000, "").SetColumn(1, new string[0], new object[0], time);
+ }
+ catch (Exception ex)
+ {
+ protocol.Log("QA" + protocol.QActionID + "|" + protocol.GetTriggerParameter() + "|Run|Exception thrown:" + Environment.NewLine + ex, LogType.Error, LogLevel.NoLogging);
+ }
+ }
+}]]>
+
+
+
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.0/HistorySets_FillArray_BulkTests.cs b/Protocol.FeaturesTests/Features/10.0/HistorySets_FillArray_BulkTests.cs
new file mode 100644
index 00000000..1b45c1a8
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.0/HistorySets_FillArray_BulkTests.cs
@@ -0,0 +1,47 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Interfaces;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ using Skyline.DataMiner.CICD.Models.Protocol.Read;
+
+ using SLDisUnitTestsShared;
+
+ [TestClass]
+ public class HistorySets_FillArray_BulkTests
+ {
+ private static HistorySets_FillArray_Bulk check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new HistorySets_FillArray_Bulk();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed_CSharp()
+ {
+ const string fileName = "HistorySets_FillArray_BulkTests.xml";
+ var input = ProtocolTestsHelper.GetProtocolInputData(fileName);
+
+ FeatureCheckContext context = new FeatureCheckContext(input, false);
+
+ IFeatureCheckResult result = check.CheckIfUsed(context);
+
+ Assert.IsTrue(result.IsUsed);
+ Assert.IsTrue(result.FeatureItems.All(x => x is CSharpFeatureCheckResultItem));
+
+ var ids = result.FeatureItems.Select(item => ((IQActionsQAction)item.Node).Id.Value);
+ var expectedIds = context.Model.Protocol.QActions.Select(qaction => qaction.Id.Value);
+ ids.Should().BeEquivalentTo(expectedIds);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.0/HistorySets_FillArray_BulkTests.xml b/Protocol.FeaturesTests/Features/10.0/HistorySets_FillArray_BulkTests.xml
new file mode 100644
index 00000000..08e0976b
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.0/HistorySets_FillArray_BulkTests.xml
@@ -0,0 +1,180 @@
+
+ Testing
+ 1.0.0.1
+
+
+ TestTable
+ Test Table
+ array
+
+
+
+
+ Test Table
+
+
+ true
+
+
+ table
+
+
+
+ TestTable_Index
+ Index (Test Table)
+ read
+
+ This is the key used internally by DataMiner to identify the table entries.
+
+
+ other
+ string
+ next param
+
+
+ true
+
+
+ string
+
+
+
+
+
+
+/// DataMiner QAction Class: Test.
+///
+public static class QAction
+{
+ ///
+ /// The QAction entry point.
+ ///
+ /// Link with SLProtocol process.
+ public static void Run(SLProtocol protocol)
+ {
+ try
+ {
+ DateTime time = new DateTime(2021, 4, 8, 8, 5, 13);
+ protocol.NotifyProtocol(193, new object[] { 1234, true, time }, new object[0]);
+ }
+ catch (Exception ex)
+ {
+ protocol.Log("QA" + protocol.QActionID + "|" + protocol.GetTriggerParameter() + "|Run|Exception thrown:" + Environment.NewLine + ex, LogType.Error, LogLevel.NoLogging);
+ }
+ }
+}]]>
+
+
+
+/// DataMiner QAction Class: Test.
+///
+public static class QAction
+{
+ ///
+ /// The QAction entry point.
+ ///
+ /// Link with SLProtocol process.
+ public static void Run(SLProtocol protocol)
+ {
+ try
+ {
+ DateTime time = new DateTime(2021, 4, 8, 8, 5, 13);
+ protocol.FillArray(1234, new object[0], time);
+ }
+ catch (Exception ex)
+ {
+ protocol.Log("QA" + protocol.QActionID + "|" + protocol.GetTriggerParameter() + "|Run|Exception thrown:" + Environment.NewLine + ex, LogType.Error, LogLevel.NoLogging);
+ }
+ }
+}]]>
+
+
+
+/// DataMiner QAction Class: Test.
+///
+public static class QAction
+{
+ ///
+ /// The QAction entry point.
+ ///
+ /// Link with SLProtocol process.
+ public static void Run(SLProtocol protocol)
+ {
+ try
+ {
+ DateTime time = new DateTime(2021, 4, 8, 8, 5, 13);
+ protocol.FillArray(1234, new List
+
+
+/// DataMiner QAction Class: Test.
+///
+public static class QAction
+{
+ ///
+ /// The QAction entry point.
+ ///
+ /// Link with SLProtocol process.
+ public static void Run(SLProtocol protocol)
+ {
+ try
+ {
+ DateTime time = new DateTime(2021, 4, 8, 8, 5, 13);
+ new QActionTable(protocol, 1324, "").FillArray(new object[0], time);
+ }
+ catch (Exception ex)
+ {
+ protocol.Log("QA" + protocol.QActionID + "|" + protocol.GetTriggerParameter() + "|Run|Exception thrown:" + Environment.NewLine + ex, LogType.Error, LogLevel.NoLogging);
+ }
+ }
+}]]>
+
+
+
+/// DataMiner QAction Class: Test.
+///
+public static class QAction
+{
+ ///
+ /// The QAction entry point.
+ ///
+ /// Link with SLProtocol process.
+ public static void Run(SLProtocol protocol)
+ {
+ try
+ {
+ DateTime time = new DateTime(2021, 4, 8, 8, 5, 13);
+ new TesttableQActionTable(protocol, 1000, "").FillArray(new object[0], time);
+ }
+ catch (Exception ex)
+ {
+ protocol.Log("QA" + protocol.QActionID + "|" + protocol.GetTriggerParameter() + "|Run|Exception thrown:" + Environment.NewLine + ex, LogType.Error, LogLevel.NoLogging);
+ }
+ }
+}]]>
+
+
+
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.0/ProtocolTtlSyntaxTests.cs b/Protocol.FeaturesTests/Features/10.0/ProtocolTtlSyntaxTests.cs
new file mode 100644
index 00000000..19c38e64
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.0/ProtocolTtlSyntaxTests.cs
@@ -0,0 +1,40 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+ using SLDisUnitTestsShared;
+
+ [TestClass]
+ public class ProtocolTtlSyntaxTests
+ {
+ private static ProtocolTtlSyntax check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new ProtocolTtlSyntax();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed()
+ {
+ const string fileName = "ProtocolTtlSyntaxTests.xml";
+ var input = ProtocolTestsHelper.GetProtocolInputData(fileName);
+
+ FeatureCheckContext context = new FeatureCheckContext(input);
+
+ var result = check.CheckIfUsed(context);
+ var expected = context.Model.Protocol.Params.Select(x => new FeatureCheckResultItem(x));
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.0/ProtocolTtlSyntaxTests.xml b/Protocol.FeaturesTests/Features/10.0/ProtocolTtlSyntaxTests.xml
new file mode 100644
index 00000000..1b54ffce
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.0/ProtocolTtlSyntaxTests.xml
@@ -0,0 +1,9 @@
+
+
+
+
+ infinite
+
+
+
+
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.0/SkipInDiagramTests.cs b/Protocol.FeaturesTests/Features/10.0/SkipInDiagramTests.cs
new file mode 100644
index 00000000..e630bbab
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.0/SkipInDiagramTests.cs
@@ -0,0 +1,41 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ [TestClass]
+ public class SkipInDiagramTests
+ {
+ private static SkipInDiagram check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new SkipInDiagram();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed()
+ {
+ const string code = "";
+ var input = new ProtocolInputData(code);
+
+ FeatureCheckContext context = new FeatureCheckContext(input);
+
+ var result = check.CheckIfUsed(context);
+ var expected = context.Model.Protocol.Chains.Select(x => new FeatureCheckResultItem(x));
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.0/TooltipTests.cs b/Protocol.FeaturesTests/Features/10.0/TooltipTests.cs
new file mode 100644
index 00000000..bead2e03
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.0/TooltipTests.cs
@@ -0,0 +1,41 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ [TestClass]
+ public class TooltipTests
+ {
+ private static Tooltip check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new Tooltip();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed()
+ {
+ const string code = "Test";
+ var input = new ProtocolInputData(code);
+
+ FeatureCheckContext context = new FeatureCheckContext(input);
+
+ var result = check.CheckIfUsed(context);
+ var expected = context.Model.Protocol.Params.Select(x => new FeatureCheckResultItem(x));
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.0/ValueMappingTests.cs b/Protocol.FeaturesTests/Features/10.0/ValueMappingTests.cs
new file mode 100644
index 00000000..de4ca085
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.0/ValueMappingTests.cs
@@ -0,0 +1,41 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ [TestClass]
+ public class ValueMappingTests
+ {
+ private static ValueMapping check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new ValueMapping();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed()
+ {
+ const string code = "";
+ var input = new ProtocolInputData(code);
+
+ FeatureCheckContext context = new FeatureCheckContext(input);
+
+ var result = check.CheckIfUsed(context);
+ var expected = context.Model.Protocol.Params.Select(x => new FeatureCheckResultItem(x));
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.1/DeleteFolder_RecycleOptionTests.cs b/Protocol.FeaturesTests/Features/10.1/DeleteFolder_RecycleOptionTests.cs
new file mode 100644
index 00000000..500d5047
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.1/DeleteFolder_RecycleOptionTests.cs
@@ -0,0 +1,47 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Interfaces;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ using Skyline.DataMiner.CICD.Models.Protocol.Read;
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+ using SLDisUnitTestsShared;
+
+ [TestClass]
+ public class DeleteFolder_RecycleOptionTests
+ {
+ private static DeleteFolder_RecycleOption check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new DeleteFolder_RecycleOption();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed_CSharp()
+ {
+ const string fileName = "DeleteFolder_RecycleOptionTests.xml";
+ var input = ProtocolTestsHelper.GetProtocolInputData(fileName);
+
+ FeatureCheckContext context = new FeatureCheckContext(input, false);
+
+ IFeatureCheckResult result = check.CheckIfUsed(context);
+
+ Assert.IsTrue(result.IsUsed);
+ Assert.IsTrue(result.FeatureItems.All(x => x is CSharpFeatureCheckResultItem));
+
+ var ids = result.FeatureItems.Select(item => ((IQActionsQAction)item.Node).Id.Value);
+ var expectedIds = context.Model.Protocol.QActions.Select(qaction => qaction.Id.Value);
+ ids.Should().BeEquivalentTo(expectedIds);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.1/DeleteFolder_RecycleOptionTests.xml b/Protocol.FeaturesTests/Features/10.1/DeleteFolder_RecycleOptionTests.xml
new file mode 100644
index 00000000..ab808cf0
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.1/DeleteFolder_RecycleOptionTests.xml
@@ -0,0 +1,32 @@
+
+ Testing
+ 1.0.0.1
+
+
+
+/// DataMiner QAction Class: Test.
+///
+public static class QAction
+{
+ ///
+ /// The QAction entry point.
+ ///
+ /// Link with SLProtocol process.
+ public static void Run(SLProtocol protocol)
+ {
+ try
+ {
+ protocol.NotifyDataMiner(182, "", false);
+ }
+ catch (Exception ex)
+ {
+ protocol.Log("QA" + protocol.QActionID + "|" + protocol.GetTriggerParameter() + "|Run|Exception thrown:" + Environment.NewLine + ex, LogType.Error, LogLevel.NoLogging);
+ }
+ }
+}]]>
+
+
+
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.1/DynamicUnitsTests.cs b/Protocol.FeaturesTests/Features/10.1/DynamicUnitsTests.cs
new file mode 100644
index 00000000..42eb26c2
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.1/DynamicUnitsTests.cs
@@ -0,0 +1,41 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ [TestClass]
+ public class DynamicUnitsTests
+ {
+ private static DynamicUnits check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new DynamicUnits();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed()
+ {
+ const string code = "";
+ var input = new ProtocolInputData(code);
+
+ FeatureCheckContext context = new FeatureCheckContext(input);
+
+ var result = check.CheckIfUsed(context);
+ var expected = context.Model.Protocol.Params.Select(x => new FeatureCheckResultItem(x));
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.1/ExecuteScriptTests.cs b/Protocol.FeaturesTests/Features/10.1/ExecuteScriptTests.cs
new file mode 100644
index 00000000..ee243459
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.1/ExecuteScriptTests.cs
@@ -0,0 +1,47 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Interfaces;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ using Skyline.DataMiner.CICD.Models.Protocol.Read;
+
+ using SLDisUnitTestsShared;
+
+ [TestClass]
+ public class ExecuteScriptTests
+ {
+ private static ExecuteScript check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new ExecuteScript();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed_CSharp()
+ {
+ const string fileName = "ExecuteScriptTests.xml";
+ var input = ProtocolTestsHelper.GetProtocolInputData(fileName);
+
+ FeatureCheckContext context = new FeatureCheckContext(input, false);
+
+ IFeatureCheckResult result = check.CheckIfUsed(context);
+
+ Assert.IsTrue(result.IsUsed);
+ Assert.IsTrue(result.FeatureItems.All(x => x is CSharpFeatureCheckResultItem));
+
+ var ids = result.FeatureItems.Select(item => ((IQActionsQAction)item.Node).Id.Value);
+ var expectedIds = context.Model.Protocol.QActions.Select(qaction => qaction.Id.Value);
+ ids.Should().BeEquivalentTo(expectedIds);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.1/ExecuteScriptTests.xml b/Protocol.FeaturesTests/Features/10.1/ExecuteScriptTests.xml
new file mode 100644
index 00000000..86340570
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.1/ExecuteScriptTests.xml
@@ -0,0 +1,32 @@
+
+ Testing
+ 1.0.0.1
+
+
+
+/// DataMiner QAction Class: Test.
+///
+public static class QAction
+{
+ ///
+ /// The QAction entry point.
+ ///
+ /// Link with SLProtocol process.
+ public static void Run(SLProtocol protocol)
+ {
+ try
+ {
+ protocol.ExecuteScript("");
+ }
+ catch (Exception ex)
+ {
+ protocol.Log("QA" + protocol.QActionID + "|" + protocol.GetTriggerParameter() + "|Run|Exception thrown:" + Environment.NewLine + ex, LogType.Error, LogLevel.NoLogging);
+ }
+ }
+}]]>
+
+
+
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.1/InternalLicenses.cs b/Protocol.FeaturesTests/Features/10.1/InternalLicenses.cs
new file mode 100644
index 00000000..bb5f85b2
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.1/InternalLicenses.cs
@@ -0,0 +1,41 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ [TestClass]
+ public class InternalLicensesTests
+ {
+ private static InternalLicenses check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new InternalLicenses();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed()
+ {
+ const string code = "";
+ var input = new ProtocolInputData(code);
+
+ FeatureCheckContext context = new FeatureCheckContext(input);
+
+ var result = check.CheckIfUsed(context);
+ var expected = context.Model.Protocol.InternalLicenses.Select(x => new FeatureCheckResultItem(x));
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.1/LoginMethod_CertificateTests.cs b/Protocol.FeaturesTests/Features/10.1/LoginMethod_CertificateTests.cs
new file mode 100644
index 00000000..d98dd2eb
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.1/LoginMethod_CertificateTests.cs
@@ -0,0 +1,41 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ [TestClass]
+ public class LoginMethod_CertificateTests
+ {
+ private static LoginMethod_Certificate check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new LoginMethod_Certificate();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed()
+ {
+ const string code = "";
+ var input = new ProtocolInputData(code);
+
+ FeatureCheckContext context = new FeatureCheckContext(input);
+
+ var result = check.CheckIfUsed(context);
+ var expected = context.Model.Protocol.HTTP.Select(x => new FeatureCheckResultItem(x));
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.1/MatrixLayoutTests.cs b/Protocol.FeaturesTests/Features/10.1/MatrixLayoutTests.cs
new file mode 100644
index 00000000..567c4a29
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.1/MatrixLayoutTests.cs
@@ -0,0 +1,41 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ [TestClass]
+ public class MatrixLayoutTests
+ {
+ private static MatrixLayout check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new MatrixLayout();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed()
+ {
+ const string code = "";
+ var input = new ProtocolInputData(code);
+
+ FeatureCheckContext context = new FeatureCheckContext(input);
+
+ var result = check.CheckIfUsed(context);
+ var expected = context.Model.Protocol.Params.Select(x => new FeatureCheckResultItem(x));
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.1/ProfileHelperTests.cs b/Protocol.FeaturesTests/Features/10.1/ProfileHelperTests.cs
new file mode 100644
index 00000000..6f4ccdee
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.1/ProfileHelperTests.cs
@@ -0,0 +1,61 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Interfaces;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ using Skyline.DataMiner.CICD.Models.Protocol.Read;
+
+ using SLDisUnitTestsShared;
+
+ [TestClass]
+ public class ProfileHelperTests
+ {
+ private static ProfileHelper check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new ProfileHelper();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed()
+ {
+ const string fileName = "ProfileHelperTests.xml";
+ var input = ProtocolTestsHelper.GetProtocolInputData(fileName);
+
+ FeatureCheckContext context = new FeatureCheckContext(input, false);
+
+ IFeatureCheckResult result = check.CheckIfUsed(context);
+
+ Assert.IsTrue(result.IsUsed);
+ Assert.IsTrue(result.FeatureItems.All(x => x is CSharpFeatureCheckResultItem));
+
+ var ids = result.FeatureItems.Select(item => ((IQActionsQAction)item.Node).Id.Value);
+ var expectedIds = context.Model.Protocol.QActions.Select(qaction => qaction.Id.Value);
+ ids.Should().BeEquivalentTo(expectedIds);
+ }
+
+ [TestMethod]
+ public void CheckIsUsed_FakeProfileHelper()
+ {
+ const string fileName = "ProfileHelperTests_Fake.xml";
+ var input = ProtocolTestsHelper.GetProtocolInputData(fileName);
+
+ FeatureCheckContext context = new FeatureCheckContext(input, false);
+
+ IFeatureCheckResult result = check.CheckIfUsed(context);
+
+ Assert.IsFalse(result.IsUsed);
+ result.FeatureItems.Should().BeEmpty();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.1/ProfileHelperTests.xml b/Protocol.FeaturesTests/Features/10.1/ProfileHelperTests.xml
new file mode 100644
index 00000000..22770559
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.1/ProfileHelperTests.xml
@@ -0,0 +1,31 @@
+
+
+
+
+/// DataMiner QAction Class: Profile Helper.
+///
+public static class QAction
+{
+ ///
+ /// The QAction entry point.
+ ///
+ /// Link with SLProtocol process.
+ public static void Run(SLProtocol protocol)
+ {
+ try
+ {
+ ProfileHelper helper = new ProfileHelper((x) => protocol.SLNet.SendMessages(x));
+ }
+ catch (Exception ex)
+ {
+ protocol.Log("QA" + protocol.QActionID + "|" + protocol.GetTriggerParameter() + "|Run|Exception thrown:" + Environment.NewLine + ex, LogType.Error, LogLevel.NoLogging);
+ }
+ }
+}]]>
+
+
+
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.1/ProfileHelperTests_Fake.xml b/Protocol.FeaturesTests/Features/10.1/ProfileHelperTests_Fake.xml
new file mode 100644
index 00000000..a0311667
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.1/ProfileHelperTests_Fake.xml
@@ -0,0 +1,39 @@
+
+
+
+
+/// DataMiner QAction Class: Profile Helper.
+///
+public static class QAction
+{
+ ///
+ /// The QAction entry point.
+ ///
+ /// Link with SLProtocol process.
+ public static void Run(SLProtocol protocol)
+ {
+ try
+ {
+ ProfileHelper helper = new ProfileHelper((x) => protocol.SLNet.SendMessages(x));
+ }
+ catch (Exception ex)
+ {
+ protocol.Log("QA" + protocol.QActionID + "|" + protocol.GetTriggerParameter() + "|Run|Exception thrown:" + Environment.NewLine + ex, LogType.Error, LogLevel.NoLogging);
+ }
+ }
+}
+
+public class ProfileHelper
+{
+ public ProfileHelper(Func messageHandler)
+ {
+
+ }
+}]]>
+
+
+
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.2/Chain_DefaultSelectionFieldTests.cs b/Protocol.FeaturesTests/Features/10.2/Chain_DefaultSelectionFieldTests.cs
new file mode 100644
index 00000000..7aa82eb7
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.2/Chain_DefaultSelectionFieldTests.cs
@@ -0,0 +1,43 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+
+
+ [TestClass]
+ public class Chain_DefaultSelectionFieldTests
+ {
+ private static Chain_DefaultSelectionField check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new Chain_DefaultSelectionField();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed()
+ {
+ const string code = "";
+ var input = new ProtocolInputData(code);
+
+ FeatureCheckContext context = new FeatureCheckContext(input);
+
+ var result = check.CheckIfUsed(context);
+ var expected = context.Model.Protocol.Chains.Select(x => new FeatureCheckResultItem(x));
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.2/Chain_GroupingNameTests.cs b/Protocol.FeaturesTests/Features/10.2/Chain_GroupingNameTests.cs
new file mode 100644
index 00000000..9fb039d3
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.2/Chain_GroupingNameTests.cs
@@ -0,0 +1,41 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ [TestClass]
+ public class Chain_GroupingNameTests
+ {
+ private static Chain_GroupingName check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new Chain_GroupingName();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed()
+ {
+ const string code = "";
+ var input = new ProtocolInputData(code);
+
+ FeatureCheckContext context = new FeatureCheckContext(input);
+
+ var result = check.CheckIfUsed(context);
+ var expected = context.Model.Protocol.Chains.Select(x => new FeatureCheckResultItem(x));
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.2/FlushPerDatagramTests.cs b/Protocol.FeaturesTests/Features/10.2/FlushPerDatagramTests.cs
new file mode 100644
index 00000000..1451d8f3
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.2/FlushPerDatagramTests.cs
@@ -0,0 +1,56 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ [TestClass]
+ public class FlushPerDatagramTests
+ {
+ private static FlushPerDatagram check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new FlushPerDatagram();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed_PortSettings()
+ {
+ const string code = "true";
+ var input = new ProtocolInputData(code);
+
+ FeatureCheckContext context = new FeatureCheckContext(input);
+
+ var result = check.CheckIfUsed(context);
+ var expected = new FeatureCheckResultItem(context.Model.Protocol.PortSettings);
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+
+ [TestMethod]
+ public void CheckIsUsed_Ports()
+ {
+ const string code = "true";
+ var input = new ProtocolInputData(code);
+
+ FeatureCheckContext context = new FeatureCheckContext(input);
+
+ var result = check.CheckIfUsed(context);
+ var expected = context.Model.Protocol.Ports.Select(x => new FeatureCheckResultItem(x));
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.3/QActionIDisposable.cs b/Protocol.FeaturesTests/Features/10.3/QActionIDisposable.cs
new file mode 100644
index 00000000..03b39469
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.3/QActionIDisposable.cs
@@ -0,0 +1,60 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Collections;
+ using System.Linq;
+
+ using FluentAssertions;
+ using FluentAssertions.Equivalency;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ using SLDisUnitTestsShared;
+
+ [TestClass]
+ public class QActionDisposableTests
+ {
+ private static QActionIDisposable check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new QActionIDisposable();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed_QActionIDisposable()
+ {
+ const string code = @"
+
+ ";
+
+ var qactionCompilationModel = ProtocolTestsHelper.GetQActionCompilationModel(code);
+ var input = new ProtocolInputData(code, qactionCompilationModel);
+
+ FeatureCheckContext context = new FeatureCheckContext(input, false);
+
+ var result = check.CheckIfUsed(context);
+ var qaction = context.Model.Protocol.QActions.First();
+ var expected = new FeatureCheckResultItem(qaction);
+
+ Assert.IsTrue(result.IsUsed);
+
+ result.FeatureItems.Should().HaveCount(1);
+ result.FeatureItems.First().Should().BeEquivalentTo(expected, option => option.IgnoringCyclicReferences());
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/10.4/ExportRule_whereAttribute.cs b/Protocol.FeaturesTests/Features/10.4/ExportRule_whereAttribute.cs
new file mode 100644
index 00000000..73af11dd
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/10.4/ExportRule_whereAttribute.cs
@@ -0,0 +1,41 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ [TestClass]
+ public class ExportRule_whereAttributeTests
+ {
+ private static ExportRule_whereAttribute check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new ExportRule_whereAttribute();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed()
+ {
+ const string code = "";
+ var input = new ProtocolInputData(code);
+
+ FeatureCheckContext context = new FeatureCheckContext(input);
+
+ var result = check.CheckIfUsed(context);
+ var expected = context.Model.Protocol.ExportRules.Select(rule => new FeatureCheckResultItem(rule));
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/8.0 (Main + Feature)/ParameterDateTimeTests.cs b/Protocol.FeaturesTests/Features/8.0 (Main + Feature)/ParameterDateTimeTests.cs
new file mode 100644
index 00000000..ded8a232
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/8.0 (Main + Feature)/ParameterDateTimeTests.cs
@@ -0,0 +1,41 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ [TestClass]
+ public class ParameterDateTimeTests
+ {
+ private static ParameterDateTime check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new ParameterDateTime();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed()
+ {
+ const string code = "";
+ var input = new ProtocolInputData(code);
+
+ FeatureCheckContext context = new FeatureCheckContext(input);
+
+ var result = check.CheckIfUsed(context);
+ var expected = context.Model.Protocol.Params.Select(x => new FeatureCheckResultItem(x));
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/8.5 (Main + Feature)/DefaultValueTests.cs b/Protocol.FeaturesTests/Features/8.5 (Main + Feature)/DefaultValueTests.cs
new file mode 100644
index 00000000..85486dd2
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/8.5 (Main + Feature)/DefaultValueTests.cs
@@ -0,0 +1,41 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ [TestClass]
+ public class DefaultValueTests
+ {
+ private static DefaultValue check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new DefaultValue();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed()
+ {
+ const string code = "";
+ var input = new ProtocolInputData(code);
+
+ FeatureCheckContext context = new FeatureCheckContext(input);
+
+ var result = check.CheckIfUsed(context);
+ var expected = context.Model.Protocol.Params.Select(x => new FeatureCheckResultItem(x));
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/9.5/ConditionalShowHidePageTests.cs b/Protocol.FeaturesTests/Features/9.5/ConditionalShowHidePageTests.cs
new file mode 100644
index 00000000..c207d35d
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/9.5/ConditionalShowHidePageTests.cs
@@ -0,0 +1,40 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+ using SLDisUnitTestsShared;
+
+ [TestClass]
+ public class ConditionalShowHidePageTests
+ {
+ private static ConditionalShowHidePage check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new ConditionalShowHidePage();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed()
+ {
+ const string fileName = "ConditionalShowHidePageTests.xml";
+ var input = ProtocolTestsHelper.GetProtocolInputData(fileName);
+
+ FeatureCheckContext context = new FeatureCheckContext(input, false);
+
+ var result = check.CheckIfUsed(context);
+ var expected = context.Model.Protocol.Display.Pages.Select(x => new FeatureCheckResultItem(x));
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/9.5/ConditionalShowHidePageTests.xml b/Protocol.FeaturesTests/Features/9.5/ConditionalShowHidePageTests.xml
new file mode 100644
index 00000000..6ffb814e
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/9.5/ConditionalShowHidePageTests.xml
@@ -0,0 +1,9 @@
+
+
+
+
+ BALABL
+
+
+
+
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/9.5/InfiniteLoopTests.cs b/Protocol.FeaturesTests/Features/9.5/InfiniteLoopTests.cs
new file mode 100644
index 00000000..64d6901c
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/9.5/InfiniteLoopTests.cs
@@ -0,0 +1,41 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ [TestClass]
+ public class InfiniteLoopTests
+ {
+ private static InfiniteLoop check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new InfiniteLoop();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed()
+ {
+ const string code = "Success";
+ var input = new ProtocolInputData(code);
+
+ FeatureCheckContext context = new FeatureCheckContext(input);
+
+ var result = check.CheckIfUsed(context);
+ var expected = context.Model.Protocol.Params.Select(x => new FeatureCheckResultItem(x));
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/9.6/ParameterSaveIntervalTests.cs b/Protocol.FeaturesTests/Features/9.6/ParameterSaveIntervalTests.cs
new file mode 100644
index 00000000..a044ab2a
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/9.6/ParameterSaveIntervalTests.cs
@@ -0,0 +1,41 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ [TestClass]
+ public class ParameterSaveIntervalTests
+ {
+ private static ParameterSaveInterval check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new ParameterSaveInterval();
+ }
+
+ [TestMethod]
+ public void CheckIsUsed()
+ {
+ const string code = "";
+ var input = new ProtocolInputData(code);
+
+ FeatureCheckContext context = new FeatureCheckContext(input);
+
+ var result = check.CheckIfUsed(context);
+ var expected = context.Model.Protocol.Params.Select(x => new FeatureCheckResultItem(x));
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Features/ExampleTests.cs b/Protocol.FeaturesTests/Features/ExampleTests.cs
new file mode 100644
index 00000000..8cf3d058
--- /dev/null
+++ b/Protocol.FeaturesTests/Features/ExampleTests.cs
@@ -0,0 +1,98 @@
+namespace SLDisDmFeatureCheckUnitTests.Features
+{
+ using System.Collections.Generic;
+ using System.Linq;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.Models.Protocol.Read;
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+
+ using SLDisDmFeatureCheck.Common;
+ using SLDisDmFeatureCheck.Common.Interfaces;
+ using SLDisDmFeatureCheck.Common.Results;
+ using SLDisDmFeatureCheck.Features;
+
+ using SLDisUnitTestsShared;
+
+ //[TestClass]
+ public class ExampleTests
+ {
+ private static ParameterDateTime check;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ check = new ParameterDateTime();
+ }
+
+ [TestMethod]
+ public void CheckReleaseNotes() /* Optional */
+ {
+ List expected = new List { 6046 };
+ check.ReleaseNotes.Should().BeEquivalentTo(expected);
+ }
+
+ [TestMethod]
+ public void CheckDescription() /* Optional */
+ {
+ string expected = "The parameter will be displayed as a DateTime. The value represents a decimal number indicating the total number of days that have passed since midnight 1899-12-30. The Interprete/Decimals tag of this parameter needs to be set to 8 to avoid rounding errors.";
+ check.Description.Should().BeEquivalentTo(expected);
+ }
+
+ [TestMethod]
+ public void CheckTitle() /* Optional */
+ {
+ string expected = "Parameter DateTime";
+ check.Title.Should().BeEquivalentTo(expected);
+ }
+
+ [TestMethod]
+ public void CheckIsUsed()
+ {
+ const string code = "";
+ var input = new ProtocolInputData(code);
+
+ FeatureCheckContext context = new FeatureCheckContext(input, false);
+
+ var result = check.CheckIfUsed(context);
+ var expected = context.Model.Protocol.Params.Select(x => new FeatureCheckResultItem(x));
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+
+ [TestMethod]
+ public void CheckIsUsed_File()
+ {
+ const string fileName = "FileName.xml";
+ var input = ProtocolTestsHelper.GetProtocolInputData(fileName);
+
+ FeatureCheckContext context = new FeatureCheckContext(input, false);
+
+ var result = check.CheckIfUsed(context);
+ var expected = context.Model.Protocol.Params.Select(x => new FeatureCheckResultItem(x));
+
+ Assert.IsTrue(result.IsUsed);
+ result.FeatureItems.Should().BeEquivalentTo(expected);
+ }
+
+ [TestMethod]
+ public void CheckIsUsed_CSharp()
+ {
+ const string fileName = "FileName.xml";
+ var input = ProtocolTestsHelper.GetProtocolInputData(fileName);
+
+ FeatureCheckContext context = new FeatureCheckContext(input, false);
+
+ IFeatureCheckResult result = check.CheckIfUsed(context);
+
+ Assert.IsTrue(result.IsUsed);
+ var ids = result.FeatureItems.Select(item => ((IQActionsQAction)item).Id.Value);
+ var expectedIds = context.Model.Protocol.QActions.Select(qaction => qaction.Id.Value);
+ ids.Should().BeEquivalentTo(expectedIds);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Generic/GenericFeatureTests.cs b/Protocol.FeaturesTests/Generic/GenericFeatureTests.cs
new file mode 100644
index 00000000..c0f562eb
--- /dev/null
+++ b/Protocol.FeaturesTests/Generic/GenericFeatureTests.cs
@@ -0,0 +1,199 @@
+namespace SLDisDmFeatureCheckUnitTests.Generic
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using SLDisDmFeatureCheck.Common.Attributes;
+ using SLDisDmFeatureCheck.Common.Interfaces;
+
+ [TestClass]
+ public class GenericFeatureTests
+ {
+ private static List<(IFeatureCheck feature, MinDataMinerVersionsAttribute minVersion, MaxDataMinerVersionsAttribute maxVersion)> features;
+
+ [ClassInitialize]
+ public static void Initialize(TestContext context)
+ {
+ features = new List<(IFeatureCheck feature, MinDataMinerVersionsAttribute minVersion, MaxDataMinerVersionsAttribute maxVersion)>();
+
+ // TODO: Can be tweaked later, maybe with inheritance?
+ var minFeatures = MinDataMinerVersionsAttribute.GetFeatures();
+ var maxFeatures = MaxDataMinerVersionsAttribute.GetFeatures();
+
+ foreach ((IFeatureCheck feature, MinDataMinerVersionsAttribute version) in minFeatures)
+ {
+ // Not ideal, but we'll need to see if we implement a comparer or something that makes it unique.
+ var temp = maxFeatures.FirstOrDefault(x => x.feature?.Title == feature.Title);
+
+ // temp will not be null, but the inner 'fields' will be default
+ features.Add((feature, version, temp.version));
+ }
+ }
+
+ [TestMethod]
+ public void CheckValidVersion()
+ {
+ List invalidFeatures = new List();
+ foreach ((IFeatureCheck feature, MinDataMinerVersionsAttribute minVersions, MaxDataMinerVersionsAttribute maxVersions) in features)
+ {
+ if (minVersions.MainRelease?.Version == null || minVersions.FeatureRelease?.Version == null)
+ {
+ invalidFeatures.Add(feature);
+ continue;
+ }
+
+ if (maxVersions != null && (maxVersions.MainRelease?.Version == null || maxVersions.FeatureRelease?.Version == null))
+ {
+ invalidFeatures.Add(feature);
+ }
+ }
+
+ if (invalidFeatures.Count <= 0)
+ {
+ return;
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ sb.AppendLine(Environment.NewLine + "Features with invalid DataMiner version(s):");
+
+ foreach (IFeature f in invalidFeatures)
+ {
+ sb.AppendLine("\t" + f.GetType().Name);
+ }
+
+ Assert.Fail(sb.ToString());
+ }
+
+ [TestMethod]
+ public void CheckValidBuildNumber()
+ {
+ List missingBuildNumbers = new List();
+ foreach ((IFeatureCheck feature, MinDataMinerVersionsAttribute minVersions, MaxDataMinerVersionsAttribute maxVersions) in features)
+ {
+ if (minVersions.MainRelease != null && minVersions.MainRelease.Iteration == 0)
+ {
+ missingBuildNumbers.Add(feature);
+ continue;
+ }
+
+ if (minVersions.FeatureRelease != null && minVersions.FeatureRelease.Iteration == 0)
+ {
+ missingBuildNumbers.Add(feature);
+ continue;
+ }
+
+ if (maxVersions == null)
+ {
+ continue;
+ }
+
+ if (maxVersions.MainRelease != null && maxVersions.MainRelease.Iteration == 0)
+ {
+ missingBuildNumbers.Add(feature);
+ continue;
+ }
+
+ if (maxVersions.FeatureRelease != null && maxVersions.FeatureRelease.Iteration == 0)
+ {
+ missingBuildNumbers.Add(feature);
+ }
+ }
+
+ if (missingBuildNumbers.Count <= 0)
+ {
+ return;
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ sb.AppendLine(Environment.NewLine + "Features with missing build number(s):");
+
+ foreach (IFeature f in missingBuildNumbers)
+ {
+ sb.AppendLine("\t" + f.GetType().Name);
+ }
+
+ Assert.Inconclusive(sb.ToString());
+ }
+
+ [TestMethod]
+ public void CheckDescription()
+ {
+ List invalidFeatures = new List();
+ foreach ((IFeatureCheck feature, _, _) in features)
+ {
+ try
+ {
+ // Will check if null, throwing exception or just empty
+ if (feature.Description.Equals(String.Empty))
+ {
+ invalidFeatures.Add(feature);
+ }
+ }
+ catch (Exception e) when (e is NullReferenceException || e is NotImplementedException)
+ {
+ invalidFeatures.Add(feature);
+ }
+ }
+
+ if (invalidFeatures.Count > 0)
+ {
+ StringBuilder sb = new StringBuilder();
+
+ sb.AppendLine(Environment.NewLine + "Features with not implemented descriptions:");
+
+ foreach (IFeature f in invalidFeatures)
+ {
+ sb.AppendLine("\t" + f.GetType().Name);
+ }
+
+ Assert.Fail(sb.ToString());
+ }
+ }
+
+ [TestMethod]
+ public void CheckReleaseNotes()
+ {
+ List invalidFeatures = new List();
+ foreach ((IFeatureCheck feature, _, _) in features)
+ {
+ // We currently didn't manage to find RNs for Class Library Ranges.
+ // JanS should be able to provide us with those but didn't fine the time of it yet.
+ if (feature.Title?.StartsWith("Class Library Range") == true)
+ {
+ continue;
+ }
+
+ try
+ {
+ // Will check if null, throwing exception or just empty
+ if (feature.ReleaseNotes.Count == 0)
+ {
+ invalidFeatures.Add(feature);
+ }
+ }
+ catch (Exception e) when (e is NullReferenceException || e is NotImplementedException)
+ {
+ invalidFeatures.Add(feature);
+ }
+ }
+
+ if (invalidFeatures.Count > 0)
+ {
+ StringBuilder sb = new StringBuilder();
+
+ sb.AppendLine(Environment.NewLine + "Features with not implemented release notes:");
+
+ foreach (IFeature f in invalidFeatures)
+ {
+ sb.AppendLine("\t" + f.GetType().Name);
+ }
+
+ Assert.Fail(sb.ToString());
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol.FeaturesTests/Protocol.FeaturesTests.csproj b/Protocol.FeaturesTests/Protocol.FeaturesTests.csproj
new file mode 100644
index 00000000..21ad171a
--- /dev/null
+++ b/Protocol.FeaturesTests/Protocol.FeaturesTests.csproj
@@ -0,0 +1,26 @@
+
+
+
+ net472;net6.0
+ disable
+ disable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Protocol/Legacy/CheckRTDisplayTrue.cs b/Protocol/Legacy/CheckRTDisplayTrue.cs
new file mode 100644
index 00000000..f46af3ac
--- /dev/null
+++ b/Protocol/Legacy/CheckRTDisplayTrue.cs
@@ -0,0 +1,120 @@
+namespace Skyline.DataMiner.ProtocolValidator
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Xml;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Interfaces;
+ using Skyline.DataMiner.CICD.Validators.Common.Model;
+
+ public partial class ProtocolChecks
+ {
+ ///
+ /// Checks the RTDisplay.
+ ///
+ /// The protocol document.
+ /// List of results.
+ public List CheckRTDisplayTrue(XmlDocument xDoc) // M
+ {
+ List resultMsg = new List();
+ XmlNamespaceManager xmlNsm = new XmlNamespaceManager(xDoc.NameTable);
+ xmlNsm.AddNamespace("slc", "http://www.skyline.be/protocol");
+
+ // Checks for parameters that MUST have RTDisplay = true
+ RtdCheckDependencyValues(xDoc, xmlNsm, ref resultMsg);
+
+ return resultMsg;
+ }
+
+ ///
+ /// Check on DependencyValues.
+ ///
+ /// The protocol document.
+ /// The namespace.
+ /// Set of allowed parameters.
+ /// List of results.
+ private void RtdCheckDependencyValues(XmlDocument xDoc, XmlNamespaceManager xmlNsm, ref List resultMsg)
+ {
+ XmlNodeList xnlParams = xDoc.SelectNodes("slc:Protocol/slc:Params/slc:Param[slc:Measurement/slc:Discreets/slc:Discreet/@dependencyValues]", xmlNsm);
+ foreach (XmlNode xnParam in xnlParams)
+ {
+ string sParamId = xnParam.Attributes?["id"].InnerXml;
+ LineNum = xnParam.Attributes?["QA_LNx"].InnerXml;
+
+ // Check Parameter Type (only write parameters)
+ XmlNode xnParamType = xnParam.SelectSingleNode("./slc:Type", xmlNsm);
+ string sParamType = xnParamType?.InnerXml;
+ if (!String.Equals(sParamType, "write"))
+ {
+ string sLineNum = xnParamType?.Attributes?["QA_LNx"].InnerXml ?? LineNum;
+
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(sLineNum),
+ ErrorId = 5302,
+ DescriptionFormat = "DependencyValues is only allowed on write parameters.",
+ DescriptionParameters = null,
+ TestName = "RtdCheckDependencyValues",
+ Severity = Severity.Major
+ });
+ }
+
+ // Get Parameter Name
+ string sParamName = xnParam.SelectSingleNode("./slc:Name", xmlNsm)?.InnerXml;
+
+ if (!String.IsNullOrWhiteSpace(sParamName) && sParamName.EndsWith("_ContextMenu"))
+ {
+ // Context Menu Parameter => Only ParameterId with placeholders. (1005;1005?;1005:default;1005?:default;1005:[value:1005];1005?:[value:1005])
+ // All transfered to new validator already
+ }
+ else
+ {
+ // No ContextMenu
+ // Check if Discreets has dependencyId attribute
+ XmlNode xnDiscreets = xnParam.SelectSingleNode("./slc:Measurement/slc:Discreets", xmlNsm);
+ string sDependencyId = xnDiscreets?.Attributes["dependencyId"]?.InnerXml;
+ if (xnDiscreets == null || sDependencyId == null)
+ {
+ string sLineNum = xnDiscreets?.Attributes?["QA_LNx"].InnerXml ?? LineNum;
+
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(sLineNum),
+ ErrorId = 5301,
+ DescriptionFormat = "Required attribute '{0}' is missing.",
+ DescriptionParameters = new object[] { "dependencyId" },
+ TestName = "RtdCheckDependencyValues",
+ Severity = Severity.Major
+ });
+ }
+
+ // Check if all discreets have the dependencyValues attribute
+ XmlNodeList xnDiscreetList = xnDiscreets?.ChildNodes;
+ foreach (XmlNode xnDiscreet in xnDiscreetList)
+ {
+ string sLineNum = xnDiscreet?.Attributes?["QA_LNx"].InnerXml ?? LineNum;
+
+ // Get dependencyValues attribute
+ string sDependencyValues = xnDiscreet?.Attributes?["dependencyValues"]?.InnerXml;
+
+ if (String.IsNullOrWhiteSpace(sDependencyValues))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(sLineNum),
+ ErrorId = 5301,
+ DescriptionFormat = "Required attribute '{0}' is missing.",
+ DescriptionParameters = new object[] { "dependencyValues" },
+ TestName = "RtdCheckDependencyValues",
+ Severity = Severity.Major
+ });
+ }
+ }
+
+ // No need to check the value from the DependencyValues attribute as it can contain anything.
+ // Maybe later => If DependencyId refers to Discreet parameter => Check if dependencyValues match with one of the discreets?
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol/Legacy/InternalError.cs b/Protocol/Legacy/InternalError.cs
new file mode 100644
index 00000000..2e343d8e
--- /dev/null
+++ b/Protocol/Legacy/InternalError.cs
@@ -0,0 +1,195 @@
+namespace Skyline.DataMiner.ProtocolValidator
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Runtime.Serialization;
+
+ using Skyline.DataMiner.CICD.Models.Protocol.Read;
+ using Skyline.DataMiner.CICD.Validators.Common.Interfaces;
+ using Skyline.DataMiner.CICD.Validators.Common.Model;
+
+ ///
+ /// Validation Result properties.
+ ///
+ ///
+ [DataContract]
+ public class InternalError : IValidationResult
+ {
+ public List SubResults { get; set; }
+
+ ///
+ /// Gets the result.
+ /// Information, Warning or Error.
+ ///
+ [DataMember(Order = 3)]
+ public Severity Severity { get; set; }
+
+ public Source Source => Source.Validator;
+
+ public List<(string Message, bool AutoFixPopup)> AutoFixWarnings { get; } = new List<(string Message, bool AutoFixPopup)>(0);
+
+ ///
+ /// Gets or sets the line number in xml file.
+ ///
+ [DataMember(Order = 4)]
+ public int Line { get; set; }
+
+ ///
+ /// Gets or sets the unique identifier for the test.
+ ///
+ public string FullId { get; set; }
+
+ ///
+ /// Gets the unique identifier for the error type.
+ ///
+ [DataMember(Order = 0)]
+ public uint ErrorId { get; set; }
+
+ /// Get the check ID.
+ public uint CheckId { get; }
+
+ ///
+ /// Gets the format for the error description.
+ ///
+ [DataMember(Order = 1)]
+ public string DescriptionFormat { get; set; }
+
+ ///
+ /// Gets or sets the name of the test.
+ ///
+ public string TestName { get; set; }
+
+ ///
+ /// Gets or sets the parameters to fill in DescriptionFormat.
+ ///
+ [DataMember(Order = 2)]
+ public object[] DescriptionParameters { get; set; }
+
+ ///
+ /// Gets the position of the result in the xml file.
+ ///
+ public int Position => -1;
+
+ public Category Category
+ {
+ get
+ {
+ return Category.Undefined;
+ }
+ }
+
+ public Certainty Certainty
+ {
+ get
+ {
+ return Certainty.Certain;
+ }
+ }
+
+ public FixImpact FixImpact
+ {
+ get
+ {
+ return FixImpact.Undefined;
+ }
+ }
+
+ public string GroupDescription
+ {
+ get
+ {
+ return String.Empty;
+ }
+ }
+
+ public string Description
+ {
+ get
+ {
+ return String.Empty;
+ }
+ }
+
+ public string HowToFix
+ {
+ get
+ {
+ return String.Empty;
+ }
+ }
+
+ public string ExampleCode
+ {
+ get
+ {
+ return String.Empty;
+ }
+ }
+
+ public string Details
+ {
+ get
+ {
+ return String.Empty;
+ }
+ }
+
+ public IReadable ReferenceNode
+ {
+ get
+ {
+ return null;
+ }
+ }
+
+ public IReadable PositionNode
+ {
+ get
+ {
+ return null;
+ }
+ }
+
+ public bool HasCodeFix
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ [DataMember(Order = 5)]
+ public (int TablePid, string Name)? DveExport { get; set; }
+
+ ///
+ /// The internal error format.
+ ///
+ private const string InternalErrorFormat = "Internal Application Error : Error in {0}. ({1}).";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public InternalError()
+ {
+ Severity = Severity.Critical;
+ ErrorId = 1001;
+ DescriptionFormat = InternalErrorFormat;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The line number.
+ /// Name of the test.
+ /// The description parameters.
+ public InternalError(int line, string testName, object[] descriptionParameters)
+ {
+ Severity = Severity.Critical;
+ ErrorId = 1001;
+ DescriptionFormat = InternalErrorFormat;
+ this.Line = line;
+ this.TestName = testName;
+ this.DescriptionParameters = descriptionParameters;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol/Legacy/ParameterInfo.cs b/Protocol/Legacy/ParameterInfo.cs
new file mode 100644
index 00000000..3d1acb8a
--- /dev/null
+++ b/Protocol/Legacy/ParameterInfo.cs
@@ -0,0 +1,254 @@
+namespace Skyline.DataMiner.ProtocolValidator
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Xml;
+
+ ///
+ /// Holds info on the parameter and its s.
+ ///
+ ///
+ public class ParameterInfo : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The parameter id.
+ /// The parameter name.
+ /// The description.
+ /// The parameter type.
+ /// Type of the interprete.
+ /// RawType of the interprete.
+ /// The type of the measurement.
+ /// If set to true [rt display].
+ /// The line number.
+ /// Type of the length.
+ /// The length type identifier.
+ /// If set to true [trended].
+ /// If set to true [alarmed].
+ /// If set to true [virtualsource].
+ /// The element.
+ /// The position.
+ public ParameterInfo(int pid, string name, string description, string type, string intType, string intRawType, string meastype, bool rtDisplay, string linenum, string lengthType, string lengthTypeId, bool trended, bool alarmed, bool virtualsource, XmlNode element, params Position[] position)
+ {
+ Pid = pid;
+ Name = name;
+ Description = description;
+ Type = type;
+ IntType = intType;
+ IntRawType = intRawType;
+ MeasType = meastype;
+ Positions = position;
+ RTDisplay = rtDisplay;
+ LineNum = linenum;
+ LengthType = lengthType;
+ LengthTypeId = lengthTypeId;
+ Trended = trended;
+ Alarmed = alarmed;
+ VirtualSource = virtualsource;
+ Element = element;
+ }
+
+ public int? DuplicateAs { get; set; }
+
+ ///
+ /// Gets a value indicating whether a Parameter is alarmed.
+ ///
+ public bool Alarmed { get; }
+
+ ///
+ /// Gets the Parameter description.
+ ///
+ public string Description { get; }
+
+ ///
+ /// Gets the element.
+ ///
+ public XmlNode Element { get; }
+
+ ///
+ /// Gets the Parameter interprete type : string, double, ...
+ ///
+ public string IntType { get; }
+
+ ///
+ /// Gets the Parameter interprete raw type : other, numeric, ...
+ ///
+ public string IntRawType { get; }
+
+ ///
+ /// Gets the length type.
+ ///
+ public string LengthType { get; }
+
+ ///
+ /// Gets the length type id.
+ ///
+ public string LengthTypeId { get; }
+
+ ///
+ /// Gets the line number.
+ ///
+ public string LineNum { get; }
+
+ ///
+ /// Gets the Parameter measurement type : string, number, discreet, ...
+ ///
+ public string MeasType { get; }
+
+ ///
+ /// Gets the Parameter name.
+ ///
+ public string Name { get; }
+
+ ///
+ /// Gets the Parameter id.
+ ///
+ public int Pid { get; }
+
+ ///
+ /// Gets the positions.
+ ///
+ public Position[] Positions { get; }
+
+ ///
+ /// Gets a value indicating whether a Parameter is needed in SLElement.
+ ///
+ public bool RTDisplay { get; }
+
+ ///
+ /// Gets a value indicating whether a Parameter is trended.
+ ///
+ public bool Trended { get; }
+
+ ///
+ /// Gets the Parameter type: read, write, ...
+ ///
+ public string Type { get; }
+
+ ///
+ /// Gets a value indicating whether a Parameter type has virtual=source attribute.
+ ///
+ public bool VirtualSource { get; }
+
+ ///
+ /// Indicates whether the current object is equal to another object of the same type.
+ ///
+ /// An object to compare with this object.
+ ///
+ /// True if the current object is equal to the parameter; otherwise, false.
+ ///
+ public bool Equals(ParameterInfo other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ bool positioncomparer = true;
+ if (Positions.Length == other.Positions.Length)
+ {
+ for (int i = 0; i < Positions.Length; i++)
+ {
+ positioncomparer &= Positions[i].Equals(other.Positions[i]);
+ }
+ }
+ else
+ {
+ positioncomparer = false;
+ }
+
+ if (Pid == other.Pid && Name == other.Name && Description == other.Description &&
+ Type == other.Type && IntType == other.IntType && MeasType == other.MeasType &&
+ positioncomparer && RTDisplay == other.RTDisplay && LineNum == other.LineNum &&
+ LengthType == other.LengthType && LengthTypeId == other.LengthTypeId &&
+ Trended == other.Trended && Alarmed == other.Alarmed && IntRawType == other.IntRawType &&
+ VirtualSource == other.VirtualSource && Element == other.Element)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Determines whether the specified , is equal to this instance.
+ ///
+ /// The to compare with this instance.
+ ///
+ /// true if the specified is equal to this instance; otherwise, false.
+ ///
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as ParameterInfo);
+ }
+
+ ///
+ /// Returns a hash code for this instance.
+ ///
+ ///
+ /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+ ///
+ public override int GetHashCode()
+ {
+ // Overflow is fine, just wrap
+ unchecked
+ {
+ int hash = 17;
+
+ // Suitable nullity checks etc, of course :)
+ hash = (hash * 23) + Pid.GetHashCode();
+ hash = (hash * 23) + Name.GetHashCode();
+ hash = (hash * 23) + Description.GetHashCode();
+ hash = (hash * 23) + Type.GetHashCode();
+ hash = (hash * 23) + IntType.GetHashCode();
+ hash = (hash * 23) + IntRawType.GetHashCode();
+ hash = (hash * 23) + MeasType.GetHashCode();
+ hash = (hash * 23) + Positions.GetHashCode();
+ hash = (hash * 23) + RTDisplay.GetHashCode();
+ hash = (hash * 23) + LineNum.GetHashCode();
+ hash = (hash * 23) + LengthType.GetHashCode();
+ hash = (hash * 23) + LengthTypeId.GetHashCode();
+ hash = (hash * 23) + Trended.GetHashCode();
+ hash = (hash * 23) + Alarmed.GetHashCode();
+ hash = (hash * 23) + VirtualSource.GetHashCode();
+ hash = (hash * 23) + Element.GetHashCode();
+ return hash;
+ }
+ }
+ }
+
+ ///
+ /// Compare two objects.
+ ///
+ ///
+ public class ParameterInfoComparer : IEqualityComparer
+ {
+ ///
+ /// Determines whether the specified objects are equal.
+ ///
+ /// The first object of type to compare.
+ /// The second object of type to compare.
+ ///
+ /// True if the specified objects are equal; otherwise, false.
+ ///
+ public bool Equals(ParameterInfo x, ParameterInfo y)
+ {
+ return x.Equals(y);
+ }
+
+ ///
+ /// Returns a hash code for this instance.
+ ///
+ /// The object.
+ ///
+ /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+ ///
+ public int GetHashCode(ParameterInfo obj)
+ {
+ return obj.GetHashCode();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol/Legacy/Position.cs b/Protocol/Legacy/Position.cs
new file mode 100644
index 00000000..0304136a
--- /dev/null
+++ b/Protocol/Legacy/Position.cs
@@ -0,0 +1,185 @@
+namespace Skyline.DataMiner.ProtocolValidator
+{
+ using System;
+ using System.Collections.Generic;
+
+ ///
+ /// Info on parameter positions.
+ ///
+ ///
+ public class Position : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class. This will be empty.
+ ///
+ public Position()
+ {
+ Page = String.Empty;
+ Row = "-1";
+ Column = "-1";
+ Export = 0;
+ }
+
+ ///
+ /// Initializes a new instance of the class without export (defaults to 0).
+ ///
+ /// The page.
+ /// The row.
+ /// The column.
+ public Position(string page, string row, string column)
+ {
+ Page = page;
+ Row = row;
+ Column = column;
+ Export = 0;
+ }
+
+ ///
+ /// Initializes a new instance of the class with export.
+ ///
+ /// The page.
+ /// The row.
+ /// The column.
+ /// Pid of the exported table.
+ public Position(string page, string row, string column, int export)
+ {
+ Page = page;
+ Row = row;
+ Column = column;
+ Export = export;
+ }
+
+ ///
+ /// Gets or sets the column.
+ ///
+ public string Column { get; set; }
+
+ ///
+ /// Gets or sets the export.
+ /// Pid of the exported table if present, 0 if not exported, -1 if export = true.
+ ///
+ public int Export { get; set; }
+
+ ///
+ /// Gets or sets the page.
+ ///
+ public string Page { get; set; }
+
+ ///
+ /// Gets or sets the row.
+ ///
+ public string Row { get; set; }
+
+ ///
+ /// Indicates whether the current object is equal to another object of the same type.
+ ///
+ /// An object to compare with this object.
+ ///
+ /// true if the current object is equal to the parameter; otherwise, false.
+ ///
+ public bool Equals(Position other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ if (Page == other.Page && Row == other.Row && Column == other.Column && Export == other.Export)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Determines whether the specified , is equal to this instance.
+ ///
+ /// The to compare with this instance.
+ ///
+ /// true if the specified is equal to this instance; otherwise, false.
+ ///
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as Position);
+ }
+
+ ///
+ /// Returns a hash code for this instance.
+ ///
+ ///
+ /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+ ///
+ public override int GetHashCode()
+ {
+ // Overflow is fine, just wrap
+ unchecked
+ {
+ int hash = 17;
+
+ // Suitable nullity checks etc, of course :)
+ hash = (hash * 23) + Page.GetHashCode();
+ hash = (hash * 23) + Row.GetHashCode();
+ hash = (hash * 23) + Column.GetHashCode();
+ hash = (hash * 23) + Export.GetHashCode();
+ return hash;
+ }
+ }
+
+ ///
+ /// Returns true if the is valid.
+ ///
+ ///
+ /// true if this instance is valid; otherwise, false.
+ ///
+ public bool IsValid()
+ {
+ return Page != String.Empty && Row != String.Empty && Column != String.Empty;
+ }
+
+ ///
+ /// Returns a that represents this instance.
+ ///
+ ///
+ /// A that represents this instance.
+ ///
+ public override string ToString()
+ {
+ return String.Format("Page: {0} Column: {1} Row: {2} Export: {3}", Page, Column, Row, Export);
+ }
+ }
+
+ ///
+ /// Compare two objects.
+ ///
+ ///
+ public class PositionComparer : IEqualityComparer
+ {
+ ///
+ /// Determines whether the specified objects are equal.
+ ///
+ /// The first object of type to compare.
+ /// The second object of type to compare.
+ ///
+ /// true if the specified objects are equal; otherwise, false.
+ ///
+ public bool Equals(Position x, Position y)
+ {
+ return x.Equals(y);
+ }
+
+ ///
+ /// Returns a hash code for this instance.
+ ///
+ /// The object.
+ ///
+ /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+ ///
+ public int GetHashCode(Position obj)
+ {
+ return obj.GetHashCode();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol/Legacy/ProtocolChecks.cs b/Protocol/Legacy/ProtocolChecks.cs
new file mode 100644
index 00000000..5e1429d7
--- /dev/null
+++ b/Protocol/Legacy/ProtocolChecks.cs
@@ -0,0 +1,4365 @@
+namespace Skyline.DataMiner.ProtocolValidator
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.Linq;
+ using System.Net;
+ using System.Text.RegularExpressions;
+ using System.Xml;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Interfaces;
+ using Skyline.DataMiner.CICD.Validators.Common.Model;
+
+ public partial class ProtocolChecks
+ {
+ #region fields and properties
+ ///
+ /// Generic line number, used for lineNum estimates in case a ProtocolChecks test fails.
+ ///
+ public string LineNum { get; set; } = "-1";
+
+ ///
+ /// List of all duplicated parameters.
+ /// Key = Duplicate parameter Id.
+ /// Value = real Parameter Id.
+ ///
+ private readonly Dictionary DuplicateParameterDictionary = new Dictionary();
+
+ ///
+ /// The groups dictionary.
+ ///
+ private readonly Dictionary GroupsDictionary = new Dictionary();
+
+ ///
+ /// List of all parameter id's in the protocol.
+ ///
+ private readonly HashSet ParameterIdSet = new HashSet();
+
+ ///
+ /// Parameter Info for all parameters.
+ /// Key = Parameter Id.
+ /// Value = ParameterInfo.
+ ///
+ private readonly Dictionary ParameterInfoDictionary = new Dictionary();
+ #endregion
+
+ ///
+ /// Checks the content of the attributes.
+ ///
+ /// The protocol document.
+ /// List of results.
+ public List CheckAttributesContent(XmlDocument xDoc) // M
+ {
+ List resultMsg = new List();
+
+ // Add xmlNameSpaceManager
+ XmlNamespaceManager xmlNsm = new XmlNamespaceManager(xDoc.NameTable);
+ xmlNsm.AddNamespace("slc", _Uri);
+
+ // Re factored into separate methods to make code more readable
+ CheckActionAttributes(xDoc, resultMsg, xmlNsm);
+ CheckChainAttributes(xDoc, resultMsg, xmlNsm);
+ CheckPairAttributes(xDoc, resultMsg, xmlNsm);
+ CheckParamAttributes(xDoc, resultMsg, xmlNsm);
+ CheckQActionAttributes(xDoc, resultMsg, xmlNsm);
+ CheckRelationAttributes(xDoc, resultMsg, xmlNsm);
+ CheckResponseAttributes(xDoc, resultMsg, xmlNsm);
+
+ return resultMsg;
+ }
+
+ ///
+ /// Checks the copy action.
+ ///
+ /// The protocol document.
+ /// List of results.
+ public List CheckCopyAction(XmlDocument xDoc)
+ {
+ List resultMsg = new List();
+
+ XmlNamespaceManager xmlNsm = new XmlNamespaceManager(xDoc.NameTable);
+ xmlNsm.AddNamespace("slc", _Uri);
+
+ XmlNodeList xnlCopyActions = xDoc.SelectNodes("/slc:Protocol/slc:Actions/slc:Action[slc:Type='copy']|.//slc:Actions/slc:Action[slc:Type='Copy']", xmlNsm);
+ if (xnlCopyActions == null)
+ {
+ return resultMsg;
+ }
+
+ foreach (XmlNode xnCopyAction in xnlCopyActions)
+ {
+ LineNum = xnCopyAction.Attributes?["QA_LNx"].InnerXml;
+ string typeIdRawValue = xnCopyAction.SelectSingleNode("./slc:Type", xmlNsm)?.Attributes?["id"]?.InnerXml;
+
+ if (String.IsNullOrEmpty(typeIdRawValue))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 3103,
+ DescriptionFormat = "Type id attribute empty or not present in Copy Action",
+ DescriptionParameters = new object[] { typeIdRawValue },
+ TestName = "CheckCopyAction",
+ Severity = Severity.Minor
+ });
+ }
+ else
+ {
+ if (!Int32.TryParse(typeIdRawValue, out int typeId)
+ || !ParameterInfoDictionary.TryGetValue(typeId, out ParameterInfo paramInfo)
+ || paramInfo?.Element == null)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 3103,
+ DescriptionFormat = "Attribute 'Action/Type@id' references a non-existing Param with ID '" + typeIdRawValue + "'.",
+ DescriptionParameters = new object[] { typeIdRawValue },
+ TestName = "CheckCopyAction",
+ Severity = Severity.Major
+ });
+
+ continue;
+ }
+
+ XmlNode xnFromParam = paramInfo.Element;
+ string rawType = xnFromParam.SelectSingleNode(".//slc:Interprete/slc:RawType", xmlNsm)?.InnerXml;
+ string lengthType = xnFromParam.SelectSingleNode(".//slc:Interprete/slc:LengthType", xmlNsm)?.InnerXml;
+
+ // Fixed and unsigned number
+ if (String.Equals(rawType, "unsigned number", StringComparison.OrdinalIgnoreCase) && String.Equals(lengthType, "fixed", StringComparison.OrdinalIgnoreCase))
+ {
+ // Get value
+ XmlNode xnValue = xnFromParam.SelectSingleNode(".//slc:Interprete/slc:Value", xmlNsm);
+ if (xnValue != null)
+ {
+ LineNum = xnValue.Attributes?["QA_LNx"].InnerXml;
+ string sValue = xnValue.InnerXml;
+ const string RegexUnsignedNumber = "(0[xX][0-9a-fA-F])+";
+ if (!Regex.IsMatch(sValue, RegexUnsignedNumber))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 3102,
+ DescriptionFormat = "Unsigned Number value '{0}' is not written in format 0xFF.",
+ DescriptionParameters = new object[] { sValue },
+ TestName = "CheckCopyAction",
+ Severity = Severity.Minor
+ });
+ }
+
+ if (sValue.Contains("0x00"))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 3101,
+ DescriptionFormat =
+ "Copy from Param with ID {0} may fail because the value contains 0x00.",
+ DescriptionParameters = new object[] { typeIdRawValue },
+ TestName = "CheckCopyAction",
+ Severity = Severity.Minor
+ });
+ }
+ }
+ }
+ }
+ }
+
+ return resultMsg;
+ }
+
+ ///
+ /// Checks the DVE column option.
+ /// Check if DVE exported table has exactly one columnoption with options=;element.
+ ///
+ /// The procotol document.
+ /// List of results.
+ public List CheckDveColumnOptionElement(XmlDocument xDoc) // M
+ {
+ List resultMsg = new List();
+
+ // Get exported table parameter ID's
+ Dictionary exportedTables = GetDveTables(xDoc);
+
+ XmlNamespaceManager xmlNsm = new XmlNamespaceManager(xDoc.NameTable);
+ xmlNsm.AddNamespace("slc", _Uri);
+
+ XmlNodeList xnlParam = xDoc.SelectNodes("slc:Protocol/slc:Params/slc:Param", xmlNsm);
+
+ foreach (XmlNode xnParam in xnlParam)
+ {
+ string parameterId = xnParam.Attributes?.GetNamedItem("id")?.InnerXml;
+ LineNum = xnParam.Attributes?.GetNamedItem("QA_LNx")?.InnerXml;
+
+ XmlNodeList xnlColumnOption = xnParam.SelectNodes("./slc:ArrayOptions/slc:ColumnOption", xmlNsm);
+
+ // Count number of columns with element option
+ int elementCounter = 0;
+
+ foreach (XmlNode columnOption in xnlColumnOption)
+ {
+ string sOptions = columnOption.Attributes?["options"]?.InnerXml;
+ if (String.IsNullOrWhiteSpace(sOptions)) { continue; }
+
+ string[] asOptions = sOptions.Split(sOptions[0]);
+ foreach (string option in asOptions)
+ {
+ if (option == "element")
+ {
+ elementCounter++;
+ }
+ }
+ }
+
+ // If element option is not present, generate error if param id is in exportedTable list
+ if (elementCounter == 0 && exportedTables.Keys.Count != 0 && exportedTables.Keys.Contains(parameterId))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2001,
+ DescriptionFormat = "There is no column with element option in exported table {0}",
+ DescriptionParameters = new object[] { parameterId },
+ TestName = "CheckDVEColumnOptionElement",
+ Severity = Severity.Major
+ });
+ }
+
+ // If exactly one element option is present and param is not in exported tables list, generate an error.
+ if (elementCounter == 1 && !exportedTables.Keys.Contains(parameterId))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2003,
+ DescriptionFormat = "There is no export table defined for table {0}",
+ DescriptionParameters = new object[] { parameterId },
+ TestName = "CheckDVEColumnOptionElement",
+ Severity = Severity.Major
+ });
+ }
+
+ if (elementCounter > 1)
+ {
+ // Generate error for multiple columns with element option
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2002,
+ DescriptionFormat = "There are multiple columns with element option in exported table {0}",
+ DescriptionParameters = new object[] { parameterId },
+ TestName = "CheckDVEColumnOptionElement",
+ Severity = Severity.Major
+ });
+
+ // Generate error if table param is not in exportedTables list.
+ if (!exportedTables.Keys.Contains(parameterId))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2003,
+ DescriptionFormat = "There is no export table defined for table {0}",
+ DescriptionParameters = new object[] { parameterId },
+ TestName = "CheckDVEColumnOptionElement",
+ Severity = Severity.Major
+ });
+ }
+ }
+ }
+
+ return resultMsg;
+ }
+
+ ///
+ /// Checks on group Settings.
+ ///
+ /// The protocol document.
+ /// List of results.
+ public List CheckGroupSettings(XmlDocument xDoc)
+ {
+ List resultMsg = new List();
+
+ XmlNamespaceManager xmlNsm = new XmlNamespaceManager(xDoc.NameTable);
+ xmlNsm.AddNamespace("slc", _Uri);
+
+ string protocolType = xDoc.SelectSingleNode("/slc:Protocol/slc:Type", xmlNsm)?.InnerXml;
+ bool bVirtual = String.Equals(protocolType, "virtual", StringComparison.OrdinalIgnoreCase);
+
+ bool bMultithreaded = false;
+ XmlNodeList xnlTimers = xDoc.SelectNodes("./slc:Timers/slc:Timer", xmlNsm);
+ foreach (XmlNode xnTimer in xnlTimers)
+ {
+ string sOptions = xnTimer?.Attributes?["options"]?.InnerXml;
+ if (String.IsNullOrEmpty(sOptions)) { continue; }
+
+ if (sOptions.StartsWith("ip:", StringComparison.InvariantCulture) || sOptions.Contains(";ip:"))
+ {
+ bMultithreaded = true;
+ }
+ }
+
+ XmlNodeList xnlGroups = xDoc.SelectNodes("/slc:Protocol/slc:Groups/slc:Group", xmlNsm);
+ foreach (XmlNode xnGroup in xnlGroups)
+ {
+ XmlNode xnContent = xnGroup.SelectSingleNode("./slc:Content", xmlNsm);
+ if (xnContent == null)
+ {
+ if (!bVirtual && !bMultithreaded)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 1901,
+ DescriptionFormat =
+ "Group with missing Content tag. This should only be used in virtual protocols or when using a multithreaded timer.",
+ DescriptionParameters = null,
+ TestName = "CheckGroupSettings",
+ Severity = Severity.Minor
+ });
+ }
+ }
+ }
+
+ return resultMsg;
+ }
+
+ ///
+ /// Checks the Interprete Measurement.
+ ///
+ /// The protocol document.
+ /// List of results.
+ public List CheckInterpreteMeasurement(XmlDocument xDoc)
+ {
+ List resultMsg = new List();
+
+ XmlNamespaceManager xmlNsm = new XmlNamespaceManager(xDoc.NameTable);
+ xmlNsm.AddNamespace("slc", _Uri);
+
+ foreach (ParameterInfo pi in ParameterInfoDictionary.Values)
+ {
+ XmlNode xnRawType = pi.Element.SelectSingleNode("./slc:Interprete/slc:RawType", xmlNsm);
+ if (xnRawType == null) { continue; }
+
+ string rawType = xnRawType.InnerXml;
+ string type = pi.IntType;
+ string measurementType = pi.MeasType;
+
+ // 3 Main Types, String, Number or Table
+ string simpleType = null;
+ switch (measurementType)
+ {
+ case "string":
+ case "pagebutton":
+ simpleType = "string";
+ break;
+
+ case "number":
+ case "analog":
+ case "chart":
+ case "digital threshold":
+ case "progress":
+ case "table":
+ case "matrix":
+ // Matrix now handled in Validator2
+ simpleType = null;
+ break;
+ case "button":
+ case "discreet":
+ case "togglebutton":
+ {
+ bool allNumbers = true;
+ XmlNode xnDiscreets = pi.Element.SelectSingleNode("./slc:Measurement/slc:Discreets", xmlNsm);
+ if (xnDiscreets != null)
+ {
+ // Check if Discreets Tag has dependencyId
+ XmlAttribute xaDependencyId = xnDiscreets.Attributes?["dependencyId"];
+
+ if (xaDependencyId == null)
+ {
+ foreach (XmlNode innerDiscreet in xnDiscreets.SelectNodes("./slc:Discreet/slc:Value", xmlNsm))
+ {
+ if (!Double.TryParse(innerDiscreet.InnerXml, NumberStyles.Any, CultureInfo.InvariantCulture, out _))
+ {
+ allNumbers = false;
+ break;
+ }
+ }
+ }
+ else
+ {
+ // Has dependencyId => String
+ allNumbers = false;
+ }
+ }
+
+ if (allNumbers)
+ {
+ simpleType = "double";
+ }
+ else
+ {
+ simpleType = "string";
+ }
+ }
+ break;
+ }
+
+ if (simpleType == null) { continue; }
+
+ if (simpleType != type)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(pi.LineNum),
+ ErrorId = 5001,
+ DescriptionFormat = "Verify Measurement - Interprete Combination for {0} : {1}",
+ DescriptionParameters = new object[] { measurementType, type },
+ TestName = "CheckInterpreteMeasurement",
+ Severity = Severity.Minor
+ });
+ }
+
+ if (measurementType != "table" && measurementType != "matrix" &&
+ (rawType == "other" && type != "string") || (rawType == "numeric text" && type != "double"))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(pi.LineNum),
+ ErrorId = 5002,
+ DescriptionFormat = "Verify Interprete RawType - Type Combination: {0} - {1}",
+ DescriptionParameters = new object[] { rawType, type },
+ TestName = "CheckInterpreteMeasurement",
+ Severity = Severity.Minor
+ });
+ }
+ }
+
+ return resultMsg;
+ }
+
+ ///
+ /// Checks the port settings.
+ ///
+ /// The protocol document.
+ /// List of results.
+ public List CheckPortSettings(XmlDocument xDoc) // SR
+ {
+ List resultMsg = new List();
+
+ XmlNamespaceManager xmlNsm = new XmlNamespaceManager(xDoc.NameTable);
+ xmlNsm.AddNamespace("slc", _Uri);
+
+ XmlNodeList nlType = xDoc.SelectNodes("slc:Protocol/slc:Type", xmlNsm);
+ foreach (XmlNode xnType in nlType)
+ {
+ LineNum = xnType.Attributes?["QA_LNx"].InnerXml;
+
+ // Check relative timers exists and is true
+ if (xnType.Attributes?["relativeTimers"] != null)
+ {
+ if (!xnType.Attributes["relativeTimers"].InnerXml.ToLower().Contains("true"))
+ {
+ // Relative timers exists but is not true
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 1802,
+ DescriptionFormat = "Main Type tag attribute relativeTimers is not set to 'true'.",
+ DescriptionParameters = null,
+ TestName = "CheckPortSettings",
+ Severity = Severity.Minor
+ });
+ }
+ }
+ else
+ {
+ // Relative timers does not exist
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 1803,
+ DescriptionFormat = "Main Type tag attribute relativeTimers must exist and be set to 'true'.",
+ DescriptionParameters = null,
+ TestName = "CheckPortSettings",
+ Severity = Severity.Minor
+ });
+ }
+ }
+
+ return resultMsg;
+ }
+
+ ///
+ /// Checks the positions.
+ ///
+ /// The protocol document.
+ /// List of results.
+ public List CheckPositions(XmlDocument xDoc) // M
+ {
+ List resultMsg = new List();
+ XmlNamespaceManager xmlNsm = new XmlNamespaceManager(xDoc.NameTable);
+ xmlNsm.AddNamespace("slc", _Uri);
+
+ Dictionary> dictUniquePositions = new Dictionary>();
+ foreach (ParameterInfo pi in ParameterInfoDictionary.Values)
+ {
+ LineNum = pi.LineNum;
+
+ if (pi.Positions != null && pi.RTDisplay)
+ {
+ foreach (Position po in pi.Positions)
+ {
+ if (!po.IsValid()) { continue; }
+
+ if (!dictUniquePositions.ContainsKey(po))
+ {
+ dictUniquePositions.Add(po, new List { Convert.ToString(pi.Pid) });
+ }
+ else
+ {
+ dictUniquePositions[po].Add(Convert.ToString(pi.Pid));
+ }
+ }
+ }
+ }
+
+ // Check duplicate positions
+ foreach (Position position in dictUniquePositions.Keys)
+ {
+ List pids = dictUniquePositions[position];
+
+ if (pids.Count >= 2)
+ {
+ bool error = true;
+ if (pids.Count == 2)
+ {
+ ParameterInfo pi1 = ParameterInfoDictionary[Convert.ToInt32(pids[0])];
+ ParameterInfo pi2 = ParameterInfoDictionary[Convert.ToInt32(pids[1])];
+ if (pi1.Description == pi2.Description)
+ {
+ // Descriptions are the same, check if types are allowed combinations
+ // Allowed combinations:
+ // read - write
+ // array - write
+ // read bit - write bit
+ // read bit - write
+
+ string type1 = pi1.Type;
+ if (type1.StartsWith("write", StringComparison.InvariantCulture))
+ {
+ type1 = "write";
+ }
+ else
+ {
+ type1 = "read";
+ }
+
+ string type2 = pi2.Type;
+ if (type2.StartsWith("write", StringComparison.InvariantCulture))
+ {
+ type2 = "write";
+ }
+ else
+ {
+ type2 = "read";
+ }
+
+ if (type1 != type2)
+ {
+ // Types are read/write pair
+ error = false;
+ }
+ }
+ // Else error: different param descriptions on same position
+ }
+
+ // If more than two parameters or different descriptions, or illegal combination, generate an error
+ if (error)
+ {
+ foreach (string spid in pids)
+ {
+ int ipid = Convert.ToInt32(spid);
+ ParameterInfo pi = ParameterInfoDictionary[ipid];
+
+ // Types are the same, these cannot be on the same position. Generate Error
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(pi.LineNum),
+ ErrorId = 2201,
+ DescriptionFormat = "There are multiple parameters on position {0}. Parameter {1} with description {2}",
+ DescriptionParameters = new object[] { position.ToString(), pi.Pid, pi.Description },
+ TestName = "CheckPositions",
+ Severity = Severity.Major
+ });
+ }
+ }
+ }
+ }
+
+ return resultMsg;
+ }
+
+ ///
+ /// Checks the protocol name.
+ ///
+ /// The protocol document.
+ /// List of results.
+ public List CheckProtocolNames(XmlDocument xDoc) // M
+ {
+ List resultMsg = new List();
+
+ XmlNamespaceManager xmlNsm = new XmlNamespaceManager(xDoc.NameTable);
+ xmlNsm.AddNamespace("slc", _Uri);
+
+ string name = null;
+ // Main protocol name
+ XmlNode xnProtocolName = xDoc.SelectSingleNode("./slc:Protocol/slc:Name", xmlNsm);
+ if (xnProtocolName != null)
+ {
+ name = xnProtocolName.InnerXml;
+ }
+
+ // Exported protocols
+ List exportProtocolNames = new List();
+
+ // Get exported protocols from type tag
+ XmlNode xnTypeOptions = xDoc.SelectSingleNode("./slc:Protocol/slc:Type/@options", xmlNsm);
+ if (xnTypeOptions != null)
+ {
+ string options = xnTypeOptions.InnerXml;
+ string[] alloptions = options.Split(';');
+ foreach (string option in alloptions)
+ {
+ if (option.StartsWith("exportProtocol", StringComparison.InvariantCulture))
+ {
+ string[] s = option.Split(':');
+ string exportprotocolname = s[1];
+ exportProtocolNames.Add(exportprotocolname);
+ }
+ }
+ }
+
+ // Get exported protocols from DVEs/DveProtocols/DveProtocol tags
+ XmlNodeList xnlDveProtocol = xDoc.SelectNodes("slc:Protocol/slc:DVEs/slc:DVEProtocols/slc:DVEProtocol", xmlNsm);
+ foreach (XmlNode dveProtocol in xnlDveProtocol)
+ {
+ LineNum = dveProtocol.Attributes?["QA_LNx"]?.InnerXml;
+ string dveName = dveProtocol.Attributes?["name"]?.InnerXml;
+ exportProtocolNames.Add(dveName);
+ }
+
+ foreach (string exportProtocolName in exportProtocolNames)
+ {
+ List badChars = CheckBadCharacters(exportProtocolName);
+ if (badChars.Count > 0)
+ {
+ string chars = String.Join(", ", badChars);
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 4902,
+ DescriptionFormat = "Exported protocol name {0} contains illegal characters {1}.",
+ DescriptionParameters = new object[] { exportProtocolName, chars },
+ TestName = "CheckProtocolNames",
+ Severity = Severity.Major
+ });
+ }
+
+ if (!exportProtocolName.StartsWith(name + " - ", StringComparison.InvariantCulture) || exportProtocolName.Trim() == (name + " -") || exportProtocolName.Trim() == (name + "-"))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 4903,
+ DescriptionFormat = "Exported protocol name \"{0}\" has incorrect format. Expected format is \"[Mother Protocol Name] - [Name]\"",
+ DescriptionParameters = new object[] { exportProtocolName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+
+ return resultMsg;
+ }
+
+ ///
+ /// Parameters with RawType double should have fixed LengthType, or be changed to numeric text.
+ ///
+ /// The protocol document.
+ /// List of results.
+ public List CheckRawTypeDouble(XmlDocument xDoc) // M
+ {
+ List resultMsg = new List();
+
+ XmlNamespaceManager xmlNsm = new XmlNamespaceManager(xDoc.NameTable);
+ xmlNsm.AddNamespace("slc", _Uri);
+
+ XmlNodeList xnlInterprete = xDoc.SelectNodes("slc:Protocol/slc:Params/slc:Param/slc:Interprete", xmlNsm);
+ foreach (XmlNode xnInterprete in xnlInterprete)
+ {
+ LineNum = xnInterprete.Attributes?["QA_LNx"].InnerXml;
+ string lengthType = String.Empty;
+ XmlNode xnRawType = xnInterprete.SelectSingleNode("./slc:RawType", xmlNsm);
+ if (xnRawType == null) { continue; }
+
+ LineNum = xnRawType.Attributes?["QA_LNx"].InnerXml;
+ string rawType = xnRawType.InnerXml;
+
+ if (!String.Equals(rawType, "double", StringComparison.OrdinalIgnoreCase)) { continue; }
+
+ XmlNode xnLengthType = xnInterprete.SelectSingleNode("./slc:LengthType", xmlNsm);
+ if (xnLengthType != null)
+ {
+ LineNum = xnLengthType.Attributes?["QA_LNx"].InnerXml;
+ lengthType = xnLengthType.InnerXml;
+ }
+
+ if (String.Equals(lengthType, "next param", StringComparison.OrdinalIgnoreCase))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2601,
+ DescriptionFormat = "RawType double is used with LengthType {0}. Change LengthType to fixed or RawType to numeric text",
+ DescriptionParameters = new object[] { lengthType },
+ TestName = "CheckSNMPRawTypeDouble",
+ Severity = Severity.Major
+ });
+ }
+ else if (String.Equals(lengthType, "fixed", StringComparison.OrdinalIgnoreCase))
+ {
+ XmlNode xnLength = xnInterprete.SelectSingleNode("./slc:Length", xmlNsm);
+ if (xnLength == null)
+ {
+ //// Covered by 2.74.1
+ ////LineNum = xnInterprete.Attributes?["QA_LNx"].InnerXml;
+ ////resultMsg.Add(new ValidationResult
+ ////{
+ //// Line = Convert.ToInt32(LineNum),
+ //// ErrorId = 2603,
+ //// DescriptionFormat = "RawType double has no length definition.",
+ //// DescriptionParameters = null,
+ //// TestName = "CheckSNMPRawTypeDouble",
+ //// Severity = Severity.Major
+ ////});
+ }
+ else
+ {
+ LineNum = xnLength.Attributes?["QA_LNx"].InnerXml;
+ string sLength = xnLength.InnerXml;
+ if (sLength != "4" && sLength != "8")
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2602,
+ DescriptionFormat = "RawType double has length {0}. Length should be 4 or 8.",
+ DescriptionParameters = new object[] { sLength },
+ TestName = "CheckSNMPRawTypeDouble",
+ Severity = Severity.Major
+ });
+ }
+ }
+ }
+ }
+
+ return resultMsg;
+ }
+
+ ///
+ /// Checks the recursive page buttons.
+ ///
+ /// The protocol document.
+ /// List of results.
+ public List CheckRecursivePageButtons(XmlDocument xDoc)
+ {
+ // Check that pageButtons are not used within a pageButton page. This crashes IE and is against design agreements.
+ List resultMsg = new List();
+ List pbPageNames = new List();
+
+ XmlNamespaceManager xmlNsm = new XmlNamespaceManager(xDoc.NameTable);
+ xmlNsm.AddNamespace("slc", _Uri);
+
+ // Find all pageButton pages and add to list
+ // .NET framework uses XPath 1.0. upper-case/lower-case are not supported, so need to use translate to perform case-insensitive check.
+ XmlNodeList xnlPageButtons = xDoc.SelectNodes("/slc:Protocol/slc:Params/slc:Param/slc:Measurement[translate(slc:Type,'PAGEBUTON','pagebuton')=\"pagebutton\"]", xmlNsm);
+
+ foreach (XmlNode pageButton in xnlPageButtons)
+ {
+ LineNum = pageButton.Attributes?["QA_LNx"].InnerXml;
+ XmlNodeList pageButtonValues = pageButton.SelectNodes("./slc:Discreets/slc:Discreet/slc:Value", xmlNsm);
+ foreach (XmlNode pageButtonValue in pageButtonValues)
+ {
+ if (pageButtonValue == null) { continue; }
+
+ LineNum = pageButtonValue.Attributes?["QA_LNx"].InnerXml;
+ string pbPageName = pageButtonValue.InnerXml;
+ if (!pbPageNames.Contains(pbPageName))
+ {
+ pbPageNames.Add(pbPageName);
+ }
+ }
+ }
+
+ // Select all pageButton parameters
+ XmlNodeList pageButtonParameters = xDoc.SelectNodes("/slc:Protocol/slc:Params/slc:Param[translate(slc:Measurement/slc:Type,'PAGEBUTON','pagebuton')=\"pagebutton\"]", xmlNsm);
+ foreach (XmlNode pageButtonParameter in pageButtonParameters)
+ {
+ LineNum = pageButtonParameter.Attributes?["QA_LNx"]?.InnerXml;
+ string id = pageButtonParameter.Attributes?["id"]?.InnerXml;
+
+ // Get parameter position pages
+ XmlNodeList xnlPages = pageButtonParameter.SelectNodes("./slc:Display/slc:Positions/slc:Position/slc:Page", xmlNsm);
+ foreach (XmlNode xnPage in xnlPages)
+ {
+ if (xnPage == null) { continue; }
+
+ LineNum = xnPage.Attributes?["QA_LNx"].InnerXml;
+ string page = xnPage.InnerXml;
+ if (pbPageNames.Contains(page))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 3401,
+ DescriptionFormat = "PageButton Parameter {0} is included on pageButton page {1}.",
+ DescriptionParameters = new object[] { id, page },
+ TestName = "CheckRecursivePageButtons",
+ Severity = Severity.Minor
+ });
+ }
+ }
+ }
+
+ return resultMsg;
+ }
+
+ ///
+ /// Checks the content of the response.
+ ///
+ /// The protocol document.
+ /// List of results.
+ public List CheckResponseContent(XmlDocument xDoc) // M
+ {
+ List resultMsg = new List();
+ XmlNamespaceManager xmlNsm = new XmlNamespaceManager(xDoc.NameTable);
+ xmlNsm.AddNamespace("slc", _Uri);
+
+ XmlNodeList xnlResponses = xDoc.SelectNodes("slc:Protocol/slc:Responses/slc:Response", xmlNsm);
+ if (xnlResponses != null)
+ {
+ foreach (XmlNode xnResponse in xnlResponses)
+ {
+ LineNum = xnResponse.Attributes?["QA_LNx"].InnerXml;
+ string responseId = xnResponse.Attributes?["id"].InnerXml;
+
+ // List of parameterInfo for all pids in response, using the same order (may contain duplicates)
+ List responseParams = new List();
+ XmlNodeList xnlRParams = xnResponse.SelectNodes("./slc:Content/slc:Param", xmlNsm);
+ foreach (XmlNode xnRParam in xnlRParams)
+ {
+ string pid = xnRParam?.InnerXml;
+
+ // ValV2 Fixed after issue found during QA of DCP97933
+ if (!String.IsNullOrEmpty(pid))
+ {
+ var paramId = Convert.ToInt32(pid);
+ if (!ParameterInfoDictionary.ContainsKey(paramId))
+ {
+ continue;
+ }
+
+ responseParams.Add(ParameterInfoDictionary[paramId]);
+ }
+ }
+
+ // Check if all parameters have fixed length
+ bool allfixed = responseParams.All(a => a.LengthType == "fixed");
+
+ // AllFixed = OK, no further tests needed
+ if (!allfixed)
+ {
+ bool prevNextParam = false;
+ bool prevTrailer = false;
+ string prevNpLtId = String.Empty;
+ string prevNpId = String.Empty;
+ string lengthParam = String.Empty;
+ for (int i = 0; i < responseParams.Count; i++)
+ {
+ ParameterInfo pi = responseParams[i];
+ bool b_last = i == responseParams.Count - 2;
+ bool last = i == responseParams.Count - 1;
+ if (pi.Type == "length")
+ {
+ lengthParam = Convert.ToString(pi.Pid);
+ }
+
+ if (pi.LengthType == "fixed")
+ {
+ if (prevNextParam)
+ {
+ // Parameter should be fixed
+ if (prevNpLtId == String.Empty)
+ {
+ if (pi.Type != "fixed" && pi.Type != "trailer")
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 4701,
+ DescriptionFormat =
+ "Next Param {0}. in response {1} is not followed by a fixed parameter or trailer.",
+ DescriptionParameters = new object[] { prevNpId, responseId },
+ TestName = "CheckResponseContent",
+ Severity = Severity.Major
+ });
+ }
+ else if (pi.Type == "fixed" || pi.Type == "trailer")
+ {
+ // Response part is closed by fixed param. Clear variables, response OK up to here
+ prevNextParam = false;
+ prevNpLtId = String.Empty;
+ prevNpId = String.Empty;
+ }
+ }
+ else // There is a lenghttype pid, loop until a fixed parameter is found
+ {
+ if (pi.Type == "fixed" || pi.Type == "trailer")
+ {
+ // Check if lengthType parameter matches
+ if (Convert.ToString(pi.Pid) == prevNpLtId)
+ {
+ // Matching parameter found, clear variables, response OK up to here
+ prevNextParam = false;
+ prevNpLtId = String.Empty;
+ prevNpId = String.Empty;
+ }
+ else // No match, throw error
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 4702,
+ DescriptionFormat =
+ "ID of fixed or trailer parameter {0} does not match lengthType id {1} in preceding Next Parameter ({2}) in response {3}.",
+ DescriptionParameters = new object[] { pi.Pid, prevNpLtId, prevNpId, responseId },
+ TestName = "CheckResponseContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ else // Not a fixed parameter
+ {
+ if (last) // Response is not closed by fixed parameter, throw error
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 4703,
+ DescriptionFormat = "Next Param {0} with lengthType id {1} followed by fixed length param is not followed by a fixed or trailer parameter in response {2}.",
+ DescriptionParameters = new object[] { prevNpId, prevNpLtId, responseId },
+ TestName = "CheckResponseContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ }
+ }
+ }
+ else if (pi.LengthType == "next param" || pi.LengthType == "last next param")
+ {
+ if (prevNextParam)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 4704,
+ DescriptionFormat = "Next Param {0} in response {1} is followed by another next param {2} without fixed separator.",
+ DescriptionParameters = new object[] { prevNpId, responseId, pi.Pid },
+ TestName = "CheckResponseContent",
+ Severity = Severity.Major
+ });
+ }
+
+ if (!last)
+ {
+ prevNextParam = true;
+ prevNpId = Convert.ToString(pi.Pid);
+ if (pi.LengthTypeId != String.Empty)
+ {
+ prevNpLtId = pi.LengthTypeId;
+ }
+ }
+ }
+
+ if (b_last && pi.Type == "trailer")
+ {
+ prevTrailer = true;
+ }
+
+ if (last)
+ {
+ bool check = pi.Type == "trailer" || lengthParam != String.Empty || prevTrailer && pi.Type == "crc";
+ if (!check)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 4705,
+ DescriptionFormat = "Response {0} has no length parameter and is not closed by a trailer parameter. This will cause the communication to wait for timeout.",
+ DescriptionParameters = new object[] { responseId },
+ TestName = "CheckResponseContent",
+ Severity = Severity.Minor
+ });
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return resultMsg;
+ }
+
+ ///
+ /// Checks the table column exports.
+ ///
+ /// The protocol document.
+ /// List of results.
+ public List CheckTableColumnExports(XmlDocument xDoc) // M
+ {
+ // TODO: Check on viewTables
+ List resultMsg = new List();
+
+ XmlNamespaceManager xmlNsm = new XmlNamespaceManager(xDoc.NameTable);
+ xmlNsm.AddNamespace("slc", _Uri);
+
+ var allTables = GetAllTables(xDoc);
+ foreach (XmlNode xnTable in allTables)
+ {
+ if (xnTable == null) { continue; }
+
+ string tableId = xnTable.Attributes?["id"].InnerXml;
+ LineNum = xnTable.Attributes?["QA_LNx"].InnerXml;
+
+ XmlAttribute xaExports = xnTable.Attributes?["export"];
+ if (xaExports == null) { continue; }
+
+ string sExports = xaExports.InnerXml;
+ string[] sTableExports = sExports.Split(';');
+ for (int i = 0; i < sTableExports.Length; i++)
+ {
+ if (sTableExports[i] == "true")
+ {
+ sTableExports[i] = "-1";
+ }
+ }
+
+ HashSet tableExports = new HashSet(sTableExports);
+ tableExports.Remove("false");
+
+ Dictionary columns = GetColumnPids(xDoc, tableId);
+ foreach (string column in columns.Keys)
+ {
+ int columnPid = Convert.ToInt32(column);
+ if (!ParameterInfoDictionary.TryGetValue(columnPid, out ParameterInfo pi))
+ {
+ pi = ParameterInfoDictionary.Values.FirstOrDefault(x => x.DuplicateAs == columnPid);
+ }
+
+ if (pi == null)
+ {
+ // Invalid Column Pid (New validator should throw error for that)
+ continue;
+ }
+
+ LineNum = pi.LineNum;
+ HashSet columnExports = new HashSet();
+ foreach (Position cpo in pi.Positions)
+ {
+ if (cpo.Export != 0)
+ {
+ columnExports.Add(cpo.Export.ToString());
+ }
+ }
+
+ if (!tableExports.SetEquals(columnExports))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 4801,
+ DescriptionFormat = "Table Column {0} has different exports from table {1}.",
+ DescriptionParameters = new object[] { column, tableId },
+ TestName = "CheckTableColumnExports",
+ Severity = Severity.Major
+ });
+ }
+ }
+ }
+
+ return resultMsg;
+ }
+
+ ///
+ /// Check dynamic table columns parameter are set correctly, including DVE tables.
+ ///
+ /// The protocol document.
+ /// List of results.
+ public List CheckTableColumnParams(XmlDocument xDoc) // M
+ {
+ List resultMsg = new List();
+
+ XmlNamespaceManager xmlNsm = new XmlNamespaceManager(xDoc.NameTable);
+ xmlNsm.AddNamespace("slc", _Uri);
+
+ // Concatenate both lists
+ var allTables = GetAllTables(xDoc);
+
+ // Gather data for DVE check
+ Dictionary exportedRootTables = GetDveTables(xDoc);
+ List allowedTables = new List(exportedRootTables.Keys);
+
+ // Check for tables with relations to exported tables
+ XmlNodeList xnlRelations = xDoc.SelectNodes("slc:Protocol/slc:Relations/slc:Relation", xmlNsm);
+ foreach (XmlNode xnRel in xnlRelations)
+ {
+ LineNum = xnRel.Attributes?["QA_LNx"].InnerXml;
+ XmlNode xnPath = xnRel.Attributes?["path"];
+ if (xnPath == null) { continue; }
+
+ string path = xnPath.InnerXml;
+ string[] relTables = path.Split(';');
+ bool exportfound = false;
+ foreach (string s in relTables)
+ {
+ if (exportfound)
+ {
+ allowedTables.Add(s);
+ }
+
+ if (exportedRootTables.ContainsKey(s))
+ {
+ exportfound = true;
+ }
+ }
+ }
+
+ foreach (XmlNode xnParam in allTables)
+ {
+ LineNum = xnParam.Attributes?.GetNamedItem("QA_LNx").InnerXml;
+ string sTablePid = xnParam.Attributes?.GetNamedItem("id")?.InnerXml;
+
+ Dictionary pidsToCheck = GetColumnPids(xDoc, sTablePid, resultMsg);
+
+ // Check if all columns in measurement are in the table.
+ HashSet measPids = GetTableMeasurementPids(xnParam, false);
+ foreach (int i in measPids)
+ {
+ if (!pidsToCheck.Keys.Contains(i.ToString()))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 1705,
+ DescriptionFormat = "Parameter {0} is included in the table measurement but not in the table definition.",
+ DescriptionParameters = new object[] { i },
+ TestName = "CheckTableColumnParams",
+ Severity = Severity.Major
+ });
+ }
+ }
+
+ // Check if measurement type is table
+ string measType = xnParam.SelectSingleNode("./slc:Measurement/slc:Type", xmlNsm)?.InnerXml;
+ LineNum = xnParam.SelectSingleNode("./slc:Measurement/slc:Type", xmlNsm)?.Attributes?["QA_LNx"].InnerXml;
+ if (measType != null && !String.Equals(measType, "table", StringComparison.OrdinalIgnoreCase) && !String.Equals(measType, "matrix", StringComparison.OrdinalIgnoreCase))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 1706,
+ DescriptionFormat = "Measurement type for array is not table or matrix.",
+ DescriptionParameters = null,
+ TestName = "CheckTableColumnParams",
+ Severity = Severity.Major
+ });
+ }
+
+ // Find foreignKeys
+ // Find tables with foreignKey to exported table that are not in a relation, in this case the table rows will be exported as standalone parameters.
+ XmlNodeList xnlCOoptions = xnParam.SelectNodes("./slc:ArrayOptions/slc:ColumnOption/@options", xmlNsm);
+ foreach (XmlNode options in xnlCOoptions)
+ {
+ string sOptions = options?.InnerXml;
+ if (String.IsNullOrEmpty(sOptions)) { continue; }
+
+ string[] allOptions = sOptions.Split(new[] { sOptions[0] }, StringSplitOptions.RemoveEmptyEntries);
+ foreach (string option in allOptions)
+ {
+ if (option.StartsWith("foreignkey=", StringComparison.InvariantCultureIgnoreCase))
+ {
+ string fkTable = option.Substring("foreignkey=".Length);
+ if (exportedRootTables.Keys.Contains(fkTable))
+ {
+ allowedTables.Add(sTablePid);
+ }
+ }
+ }
+ }
+
+ // Check on table columns
+ foreach (string sPidToCheck in pidsToCheck.Keys)
+ {
+ if (Int32.TryParse(sPidToCheck, out int iPidToCheck))
+ {
+ ParameterInfo piTable = ParameterInfoDictionary[Convert.ToInt32(sTablePid)];
+
+ // In case sPidToCheck relates to a parameter made by the duplicateAs attribute (meant to be used in case of viewTables)
+ int iRealPidToCheck = GetRealPid(iPidToCheck);
+ if (!ParameterIdSet.Contains(Convert.ToString(iRealPidToCheck)))
+ {
+ // Retrieve correct Line Number of ColumnOption. Current LineNum is from Measurement Type Tag.
+ if (!Int32.TryParse(xnParam.SelectSingleNode("./slc:ArrayOptions/slc:ColumnOption[@pid='" + iPidToCheck + "']", xmlNsm)?.Attributes?["QA_LNx"].InnerXml, out int iTempLineNum))
+ {
+ // For some reason ColumnOption tag can't be found. Assign Table LineNumber.
+ iTempLineNum = Convert.ToInt32(piTable.LineNum);
+ }
+
+ continue;
+ }
+
+ ParameterInfo pi = ParameterInfoDictionary[iRealPidToCheck];
+ bool columnPositionAllowed = allowedTables.Contains(sTablePid);
+ bool tableDisplayedInExport = piTable.Positions.Any(x => x.Export != 0);
+ bool validPosition = pi.Positions.Any(x => x.IsValid());
+
+ if (!validPosition)
+ {
+ continue;
+ }
+
+ if (columnPositionAllowed && !tableDisplayedInExport)
+ {
+ continue;
+ }
+
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(pi.LineNum),
+ ErrorId = 1701,
+ DescriptionFormat = "Table column parameter {0} should not contain Positions tag unless exported as standalone parameter.",
+ DescriptionParameters = new object[] { pi.Pid },
+ TestName = "CheckTableColumnParams",
+ Severity = Severity.Major
+ });
+ }
+ }
+ }
+
+ return resultMsg;
+ }
+
+ ///
+ /// Checks the table index sequence.
+ ///
+ /// The protocol document.
+ /// List of results.
+ public List CheckTableIndexSequence(XmlDocument xDoc) // M
+ {
+ // Check that all table column indexes are sequential
+
+ List resultMsg = new List();
+
+ XmlNameTable nt = xDoc.NameTable;
+ XmlNamespaceManager xmlNsm = new XmlNamespaceManager(nt);
+ xmlNsm.AddNamespace("slc", _Uri);
+
+ // Params with arrayOptions = table
+ XmlNodeList xnlArrayOptions = xDoc.GetElementsByTagName("ArrayOptions");
+
+ foreach (XmlNode xnArrayOptions in xnlArrayOptions)
+ {
+ LineNum = xnArrayOptions.Attributes?["QA_LNx"]?.InnerXml;
+ XmlNodeList xnlColumnOptions = xnArrayOptions.SelectNodes("slc:ColumnOption", xmlNsm);
+
+ List typePids = new List();
+ List idxList = new List();
+ List types = new List();
+ int indexTypeIdx = -1;
+
+ if (xnArrayOptions.Attributes?["index"] != null)
+ {
+ // Get column parameter id's in Type Tag
+ string id = xnArrayOptions.ParentNode?.SelectSingleNode(".//slc:Type", xmlNsm)?.Attributes?["id"]?.InnerXml;
+ if (id != null)
+ {
+ string[] typeIds = id.Split(';');
+ foreach (string s in typeIds)
+ {
+ if (Int32.TryParse(s, out int i))
+ {
+ typePids.Add(i);
+ }
+ }
+ }
+
+ // Get column parameter id's , indexes and types in columnOptions
+ foreach (XmlNode columnOption in xnlColumnOptions)
+ {
+ string sIdx = columnOption.Attributes?["idx"]?.InnerXml;
+ string sPid = columnOption.Attributes?["pid"]?.InnerXml;
+ string type = columnOption.Attributes?["type"]?.InnerXml;
+
+ if (type != null)
+ {
+ types.Add(type);
+ if (type == "index" && indexTypeIdx == -1)
+ {
+ indexTypeIdx = Convert.ToInt32(sIdx);
+ }
+ }
+
+ if (Int32.TryParse(sIdx, out int iIdx) && Int32.TryParse(sPid, out int iPid))
+ {
+ idxList.Add(iIdx);
+ }
+ }
+
+ // Perform Checks
+ bool warn = false;
+ bool error = false;
+
+ int typeCounter = typePids.Count;
+ for (int i = 0; i < idxList.Count - 1; i++) // Avoid out of range exception on last item
+ {
+ if (idxList[i] > idxList[i + 1])
+ {
+ error = true;
+ break;
+ }
+ }
+
+ for (int i = 0; i < idxList.Count; i++)
+ {
+ if (idxList[i] != i + typeCounter)
+ {
+ if (typeCounter == 0)
+ {
+ error = true;
+ break;
+ }
+
+ if (idxList[i] != i)
+ {
+ typeCounter--;
+ }
+
+ warn = true;
+ }
+ }
+
+ if (error)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 3501,
+ DescriptionFormat = "Indexes of table are not sequential.",
+ DescriptionParameters = null,
+ TestName = "CheckTableIndexSequence",
+ Severity = Severity.Major
+ });
+ }
+ else if (warn)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 3502,
+ DescriptionFormat = "Parameter indexes in Type and ColumnOptions are not consecutive. This is unconventional.",
+ DescriptionParameters = null,
+ TestName = "CheckTableIndexSequence",
+ Severity = Severity.Minor
+ });
+ }
+ }
+
+ // Check for type="index": can occur only once
+ int indexCount = types.Count(a => a == "index");
+ if (indexCount > 1)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 3503,
+ DescriptionFormat = "There is more than one ColumnOption with type=\"index\"",
+ DescriptionParameters = null,
+ TestName = "CheckTableIndexSequence",
+ Severity = Severity.Major
+ });
+ }
+ else if (indexCount == 1)
+ {
+ // Check for type="index": only allowed on SNMP and WMI tables
+ XmlNode xnSnmp = xnArrayOptions.ParentNode?.SelectSingleNode("./slc:SNMP", xmlNsm);
+ if (xnSnmp == null)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 3504,
+ DescriptionFormat = "ColumnOption with type=\"index\" is used on a non-SNMP table",
+ DescriptionParameters = null,
+ TestName = "CheckTableIndexSequence",
+ Severity = Severity.Minor
+ });
+ }
+
+ // Verify that index defined in ArrayOptions matches ColumnOption with index
+ // Get index column
+ string sIndex = xnArrayOptions?.Attributes?["index"]?.InnerXml;
+ if (sIndex != indexTypeIdx.ToString())
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 3505,
+ DescriptionFormat = "ColumnOption with type=\"index\" does not match table index idx.",
+ DescriptionParameters = null,
+ TestName = "CheckTableIndexSequence",
+ Severity = Severity.Minor
+ });
+ }
+ }
+ }
+
+ return resultMsg;
+ }
+
+ ///
+ /// Checks the timers.
+ ///
+ /// The protocol document.
+ /// List of results.
+ public List CheckTimers(XmlDocument xDoc)
+ {
+ List resultMsg = new List();
+
+ XmlNamespaceManager xmlNsm = new XmlNamespaceManager(xDoc.NameTable);
+ xmlNsm.AddNamespace("slc", _Uri);
+
+ XmlNodeList xnlTimers = xDoc.SelectNodes("/slc:Protocol/slc:Timers/slc:Timer", xmlNsm);
+
+ List lsTimes = new List();
+ foreach (XmlNode xnTimer in xnlTimers)
+ {
+ LineNum = xnTimer.Attributes?["QA_LNx"].InnerXml;
+
+ // Check duplicate speeds
+ string speed = xnTimer.SelectSingleNode("./slc:Time", xmlNsm)?.InnerXml;
+ if (!lsTimes.Contains(speed))
+ {
+ lsTimes.Add(speed);
+ }
+
+ // Check that last group is a poll group.
+ XmlNode xnLastGroup = xnTimer.SelectSingleNode(".//slc:Content/slc:Group[last()]", xmlNsm);
+ string sType = String.Empty;
+ bool emptyTimer = false;
+ if (xnLastGroup == null)
+ {
+ emptyTimer = true;
+ }
+ else
+ {
+ string groupId = xnLastGroup.InnerXml;
+ if (!String.IsNullOrWhiteSpace(groupId) && Int32.TryParse(groupId, out int iGroupId))
+ {
+ if (GroupsDictionary.TryGetValue(iGroupId, out XmlNode xnGroup))
+ {
+ XmlNode xnType = xnGroup.SelectSingleNode(".//slc:Type", xmlNsm);
+ if (xnType != null)
+ {
+ sType = xnType.InnerXml;
+ }
+ else
+ {
+ XmlNode xnContent = xnGroup.SelectSingleNode("./slc:Content", xmlNsm);
+ if (xnContent != null)
+ {
+ int paramCounter = 0;
+ int pairCounter = 0;
+ int sessionCounter = 0;
+ XmlNodeList xnlContent = xnContent.ChildNodes;
+ int count = 0;
+ foreach (XmlNode xnContentChild in xnlContent)
+ {
+ if (xnContentChild.NodeType != XmlNodeType.Comment)
+ {
+ count++;
+ switch (xnContentChild.Name)
+ {
+ case "Param":
+ paramCounter++;
+ break;
+
+ case "Pair":
+ pairCounter++;
+ break;
+
+ case "Session":
+ sessionCounter++;
+ break;
+ }
+ }
+ }
+
+ if (paramCounter == count)
+ {
+ sType = "_allParams";
+ }
+ else if (pairCounter == count)
+ {
+ sType = "_allPairs";
+ }
+ else if (sessionCounter == count)
+ {
+ sType = "_allSessions";
+ }
+ }
+ }
+ }
+ }
+ }
+
+ List pollTypes = new List { "poll", "poll action", "poll trigger" };
+
+ if (!pollTypes.Contains(sType.ToLower()) && sType != "_allParams" && sType != "_allPairs" && sType != "_allSessions" && !emptyTimer)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 3202,
+ DescriptionFormat = "The last group in the timer is not a poll group.",
+ DescriptionParameters = null,
+ TestName = "CheckTimers",
+ Severity = Severity.Minor
+ });
+ }
+
+ // Check that non-threaded timers contain no empty groups
+ string timerOptions = xnTimer.Attributes?["options"]?.InnerXml?.ToLower();
+
+ bool threaded = !String.IsNullOrEmpty(timerOptions) && timerOptions.Contains("threadpool");
+
+ if (!threaded)
+ {
+ // Get groups
+ bool empty = false;
+ List emptyIDs = new List();
+ XmlNodeList xnlTGroups = xnTimer.SelectNodes("./slc:Content/slc:Group", xmlNsm);
+ foreach (XmlNode xnTGroup in xnlTGroups)
+ {
+ if (xnTGroup == null) { continue; }
+
+ string groupId = xnTGroup.InnerXml;
+
+ if (!Int32.TryParse(groupId, out int iGroupId))
+ {
+ continue;
+ }
+
+ if (!GroupsDictionary.TryGetValue(iGroupId, out XmlNode xnGroup))
+ {
+ continue;
+ }
+
+ if (xnGroup == null) { continue; }
+
+ XmlNode xnContent = xnGroup.SelectSingleNode("./slc:Content", xmlNsm);
+ if (xnContent == null || xnContent.ChildNodes.Count == 0)
+ {
+ empty = true;
+ emptyIDs.Add(groupId);
+ }
+ else
+ {
+ foreach (XmlNode xnContentChild in xnContent.ChildNodes)
+ {
+ if (xnContentChild.NodeType == XmlNodeType.Comment) { continue; }
+
+ if (xnContentChild.InnerXml == String.Empty)
+ {
+ empty = true;
+ emptyIDs.Add(groupId);
+ break;
+ }
+ }
+ }
+ }
+
+ if (empty)
+ {
+ string groups = String.Join(", ", emptyIDs);
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 3203,
+ DescriptionFormat = "Timer contains empty Group(s) {0}.",
+ DescriptionParameters = new object[] { groups },
+ TestName = "CheckTimers",
+ Severity = Severity.Major
+ });
+ }
+ }
+ }
+
+ return resultMsg;
+ }
+
+ ///
+ /// Check that there is no alarming or trending on write parameters.
+ /// Check that parameters with explicit trending = true have RTDisplay = true.
+ ///
+ /// The protocol document.
+ /// List of results.
+ public List CheckTrendAlarm(XmlDocument xDoc) // M
+ {
+ List resultMsg = new List();
+ XmlNamespaceManager xmlNsm = new XmlNamespaceManager(xDoc.NameTable);
+ xmlNsm.AddNamespace("slc", _Uri);
+
+ // Concatenate both lists
+ var allTables = GetAllTables(xDoc);
+
+ List columnids = new List();
+ foreach (XmlNode xnTable in allTables)
+ {
+ string id = xnTable.Attributes?["id"].InnerXml;
+ Dictionary columns = GetColumnPids(xDoc, id);
+ foreach (string s in columns.Keys)
+ {
+ columnids.Add(s);
+ }
+ }
+
+ foreach (ParameterInfo pi in ParameterInfoDictionary.Values)
+ {
+ LineNum = pi.LineNum;
+ if (pi.Trended && pi.Alarmed && pi.Type.Contains("write"))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(pi.LineNum),
+ ErrorId = 2405,
+ DescriptionFormat = "Write Parameter is trended and alarmed",
+ DescriptionParameters = null,
+ TestName = "CheckTrendAlarm",
+ Severity = Severity.Major
+ });
+ }
+ else if (pi.Trended && pi.Type.Contains("write"))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(pi.LineNum),
+ ErrorId = 2401,
+ DescriptionFormat = "Write Parameter is trended",
+ DescriptionParameters = null,
+ TestName = "CheckTrendAlarm",
+ Severity = Severity.Major
+ });
+ }
+ else if (pi.Alarmed && pi.Type.Contains("write"))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(pi.LineNum),
+ ErrorId = 2402,
+ DescriptionFormat = "Write Parameter is alarmed",
+ DescriptionParameters = null,
+ TestName = "CheckTrendAlarm",
+ Severity = Severity.Major
+ });
+ }
+
+ // Check positions
+ bool anyPosition = pi.Positions.Any(x => x.IsValid());
+
+ // Check if table column
+ bool displayed = pi.RTDisplay && (anyPosition || columnids.Contains(Convert.ToString(pi.Pid)));
+ if (pi.Trended && pi.Alarmed && !displayed && pi.Type.Contains("read"))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(pi.LineNum),
+ ErrorId = 2406,
+ DescriptionFormat = "Parameter {0} has trending=\"true\" and is alarmed but is not displayed on any page, which is inconsistent, please verify.",
+ DescriptionParameters = new object[] { pi.Pid },
+ TestName = "CheckTrendAlarm",
+ Severity = Severity.Minor
+ });
+ }
+ else if (pi.Trended && !displayed && pi.Type.Contains("read"))
+ {
+ string value = pi.Element.Attributes?["trending"]?.Value;
+
+ if (String.Equals(value, "true", StringComparison.OrdinalIgnoreCase))
+ {
+ resultMsg.Add(new ValidationResult()
+ {
+ Line = Convert.ToInt32(pi.LineNum),
+ ErrorId = 2403,
+ DescriptionFormat = "Parameter {0} has trending=\"true\" but is not displayed on any page, which is inconsistent, please verify.",
+ DescriptionParameters = new object[] { pi.Pid },
+ TestName = "CheckTrendAlarm",
+ Severity = Severity.Major
+ });
+ }
+ }
+ else if (pi.Alarmed && !displayed && pi.Type.Contains("read"))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(pi.LineNum),
+ ErrorId = 2404,
+ DescriptionFormat = "Parameter {0} is alarmed but is not displayed on any page, which is inconsistent, please verify.",
+ DescriptionParameters = new object[] { pi.Pid },
+ TestName = "CheckTrendAlarm",
+ Severity = Severity.Minor
+ });
+ }
+ }
+
+ return resultMsg;
+ }
+
+ ///
+ /// Checks the action attributes.
+ ///
+ /// The protocol document.
+ /// List of results.
+ /// The namespace.
+ private void CheckActionAttributes(XmlDocument xDoc, List resultMsg, XmlNamespaceManager xmlNsm)
+ {
+ string[] operators = { "<", ">", "==", "<=", ">=", "!=", "<", ">", "<=", ">=" };
+
+ // Action.On => done in CheckResponsePairGroup
+ // Action.Type options: semicolon separated.
+ XmlNodeList xnlActionTypes = xDoc.SelectNodes("slc:Protocol/slc:Actions/slc:Action/slc:Type[@options]", xmlNsm);
+ foreach (XmlNode xnActionType in xnlActionTypes)
+ {
+ LineNum = xnActionType.Attributes?["QA_LNx"].InnerText;
+ XmlNode xnActionTypeOptions = xnActionType.Attributes?.GetNamedItem("options");
+ if (xnActionTypeOptions == null) { continue; }
+
+ string actiontype = xnActionType.InnerXml.ToLower();
+ if (actiontype == "aggregate")
+ {
+ string[] aggregateOptions = { "type", "groupby", "groupbytable", "equation", "equationvalue", "return", "result", "allowvalues", "ignorevalues", "threaded", "filter", "avoidzeroinresult", "join", "status", "defaultvalue", "defaultif", "weight", "deletehistory" };
+ string[] aggregateTypes = { "pct", "avg", "max", "min", "most", "range", "stddev", "count", "sum", "most count", "meandev", "dbmv", "db", "avg extended" };
+ string sActionTypeOptions = xnActionTypeOptions.InnerXml;
+ sActionTypeOptions = WebUtility.HtmlDecode(sActionTypeOptions);
+ string[] actionTypeOptions = sActionTypeOptions.Split(';');
+ string optionName = String.Empty;
+ foreach (string actionTypeOption in actionTypeOptions)
+ {
+ bool starter = false;
+ foreach (string option in aggregateOptions)
+ {
+ if (actionTypeOption.StartsWith(option, StringComparison.InvariantCultureIgnoreCase))
+ {
+ starter = true;
+ optionName = option;
+ }
+ }
+
+ if (!starter)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2901,
+ DescriptionFormat = "Unknown or malformed {0} option '{1}'.",
+ DescriptionParameters = new object[] { "Action Type Aggregation", actionTypeOption },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ else
+ {
+ string[] optionContent = actionTypeOption.Split(':');
+ switch (optionName)
+ {
+ case "type":
+ {
+ // Check if the aggregation type is known
+ string aggregateType = optionContent[1].ToLower();
+ if (!aggregateTypes.Contains(aggregateType))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2902,
+ DescriptionFormat = "Unknown or malformed {0} type {1}.",
+ DescriptionParameters = new object[] { actiontype, aggregateType },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ }
+ break;
+
+ case "groupby":
+ {
+ // These are column indexes: should be a number or a comma separated id list of numbers.
+ string groupby = optionContent[1].ToLower();
+ string[] idxs = groupby.Split(',');
+
+ // Check that values are numeric
+ if (idxs.All(a => Int32.TryParse(a, out int id))) { continue; }
+
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2903,
+ DescriptionFormat = "{0} option contains an invalid separator or character. This should be a single column index or a comma separated list of column indexes.",
+ DescriptionParameters = new object[] { optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ break;
+
+ case "allowValues":
+ case "ignoreValues":
+ {
+ // These should be a single id or a comma separated id list.
+ string groupby = optionContent[1].ToLower();
+ string[] values = groupby.Split(',');
+ foreach (string value in values)
+ {
+ if (!value.Contains('/')) { continue; }
+
+ string[] columnTest = value.Split('/');
+ if (IsInteger(columnTest[0])) { continue; }
+
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2966,
+ DescriptionFormat = "{0} option contains an invalid separator or character. This should be comma separated list of \"idx/value\" pairs.",
+ DescriptionParameters = new object[] { optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ break;
+
+ case "return":
+ {
+ // These should be a single id or a comma separated id list.
+ string returnPids = optionContent[1].ToLower();
+ string[] pids = returnPids.Split(',');
+ foreach (string pid in pids)
+ {
+ if (IsInteger(pid)) // Check if value is single number
+ {
+ if (ParameterIdSet.Contains(pid)) { continue; }
+
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2952,
+ DescriptionFormat = "Parameter with ID {0} not found.",
+ DescriptionParameters = new object[] { pid },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ else
+ {
+ // Create invalid id error
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2904,
+ DescriptionFormat = "{0} option contains an invalid character. This should be a single parameter ID.",
+ DescriptionParameters = new object[] { optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ }
+ break;
+
+ case "groupbyTable":
+ case "result":
+ case "status":
+ case "weight":
+ {
+ // These should contain a single id .
+ string pid = optionContent[1].ToLower();
+ if (IsInteger(pid)) // Check if value is single number
+ {
+ if (ParameterIdSet.Contains(pid)) { continue; }
+
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2952,
+ DescriptionFormat = "Parameter with ID {0} not found.",
+ DescriptionParameters = new object[] { pid },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ else
+ {
+ // Create invalid id error
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2904,
+ DescriptionFormat = "{0} option contains an invalid character. This should be a single parameter ID.",
+ DescriptionParameters = new object[] { optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ break;
+
+ case "equation":
+ {
+ // Format should be Operator - comma - pid
+ string equation = optionContent[1].ToLower();
+ string[] operatorPid = equation.Split(',');
+ if (operatorPid.Length == 2)
+ {
+ string op = operatorPid[0];
+ string pid = operatorPid[1];
+ if (!operators.Contains(op))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2905,
+ DescriptionFormat = "Equation operator {0} is invalid.",
+ DescriptionParameters = new object[] { op },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+
+ if (Int32.TryParse(pid, out int id)) // Check if value is single number
+ {
+ if (ParameterIdSet.Contains(pid)) { continue; }
+
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2952,
+ DescriptionFormat = "Parameter with ID {0} not found.",
+ DescriptionParameters = new object[] { pid },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ else
+ {
+ // Create invalid id error
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2906,
+ DescriptionFormat = "ID {0} in equation is not correctly formatted.",
+ DescriptionParameters = new object[] { pid },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ else
+ {
+ // Format is generally incorrect
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2907,
+ DescriptionFormat = "Equation is not correctly formatted. Format should be \"Operator - Comma - PID\".",
+ DescriptionParameters = null,
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ break;
+
+ case "equationvalue":
+ {
+ string equationvalue = optionContent[1].ToLower();
+ string[] equationvalues = equationvalue.Split(',');
+ if (equationvalues.Length != 4 && equationvalues.Length != 3)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2904,
+ DescriptionFormat = "{0} action {1} contains {2} parameters. 3 or 4 Parameters are expected.",
+ DescriptionParameters = new object[] { actiontype, optionName, equationvalues.Length },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ else
+ {
+ string op = equationvalues[0];
+ ////string compareto = equationvalues[1];
+ string pid = equationvalues[2];
+ ////string instance = equationvalues[3];
+
+ if (!operators.Contains(op))
+ {
+ // Create invalid operator error
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2905,
+ DescriptionFormat = "Equation operator {0} is invalid.",
+ DescriptionParameters = new object[] { op },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+
+ if (!ParameterIdSet.Contains(pid))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2952,
+ DescriptionFormat = "Parameter with ID {0} not found.",
+ DescriptionParameters = new object[] { pid },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ }
+ break;
+
+ case "JOIN":
+ {
+ // These should be a a comma separated id list.
+ string groupby = optionContent[1].ToLower();
+
+ string[] ids = groupby.Split(',');
+ if (ids.All(a => IsInteger(a)))
+ {
+ foreach (string id in ids)
+ {
+ if (ParameterIdSet.Contains(id)) { continue; }
+
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2952,
+ DescriptionFormat = "Parameter with ID {0} not found.",
+ DescriptionParameters = new object[] { id },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ else
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2908,
+ DescriptionFormat = "JOIN option contains an invalid separator or character. This should be a comma separated list of ID's.",
+ DescriptionParameters = null,
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ break;
+
+ case "threaded":
+ case "avoidzeroinresult":
+ {
+ if (optionContent.Length > 1)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2911,
+ DescriptionFormat = "Option {0} does not require a value.",
+ DescriptionParameters = new object[] { optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ break;
+
+ case "defaultvalue":
+ {
+ string[] pidValue = optionContent[1].Split(',');
+ string spid = pidValue[0];
+ if (Int32.TryParse(spid, out int pid))
+ {
+ if (ParameterIdSet.Contains(spid)) { continue; }
+
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2903,
+ DescriptionFormat = "Aggregation action {0} contains non-existing parameter id '{1}'.",
+ DescriptionParameters = new object[] { optionName, pid },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ else
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2906,
+ DescriptionFormat = "ID {0} in merge action {1} option is not correctly formatted.",
+ DescriptionParameters = new object[] { pid, optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ break;
+
+ case "defaultif":
+ {
+ string[] idxValue = optionContent[1].Split(',');
+ string idx = idxValue[0];
+ if (IsInteger(idx)) { continue; }
+
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2906,
+ DescriptionFormat = "index {0} in merge action {1} option is not correctly formatted.",
+ DescriptionParameters = new object[] { idx, optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ break;
+ }
+ }
+ }
+ }
+ else if (actiontype == "merge")
+ {
+ string[] mergeOptions = { "remoteelements", "trigger", "destination", "destinationfindpk", "resolve", "type", "mergeresult", "deletehistory", "defaultvalue", "defaultif", "limitresult" };
+ List mergeTypes = new List { "pct", "avg", "max", "min", "most", "range", "stddev", "count", "sum", "most count", "meandev", "dbmv", "db", "avg extended" };
+ string sActionTypeOptions = xnActionTypeOptions.InnerXml;
+ sActionTypeOptions = WebUtility.HtmlDecode(sActionTypeOptions);
+ string[] actionTypeOptions = sActionTypeOptions.Split(';');
+ string optionName = String.Empty;
+ foreach (string actionTypeOption in actionTypeOptions)
+ {
+ bool starter = false;
+ foreach (string option in mergeOptions)
+ {
+ if (actionTypeOption.StartsWith(option, StringComparison.InvariantCultureIgnoreCase))
+ {
+ starter = true;
+ optionName = option;
+ }
+ }
+
+ if (!starter)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2901,
+ DescriptionFormat = "Unknown or malformed {0} option '{1}'.",
+ DescriptionParameters = new object[] { "Action Type Merge", actionTypeOption },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ else
+ {
+ string type = String.Empty;
+ string[] optionContent = actionTypeOption.Split(new[] { ':' }, 2);
+ switch (optionName)
+ {
+ case "type":
+ {
+ // Check if the aggregation type is known
+ type = optionContent[1].ToLower();
+ if (mergeTypes.Contains(type)) { continue; }
+
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2902,
+ DescriptionFormat = "Unknown or malformed {0} type {1}.",
+ DescriptionParameters = new object[] { actiontype, type },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ break;
+
+ case "destination":
+ {
+ if (type == "avg extended") // 4 column pids expected
+ {
+ string content = optionContent[1];
+ if (content.Contains(','))
+ {
+ string[] ids = content.Split(',');
+ if (ids.Length == 4)
+ {
+ foreach (string id in ids)
+ {
+ string pid = id;
+
+ // Check for 1:200 format
+ if (id.Contains(':'))
+ {
+ pid = id.Split(':')[1];
+ }
+
+ if (ParameterIdSet.Contains(pid)) { continue; }
+
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2903,
+ DescriptionFormat = "Aggregation action destination contains non-existing parameter id '{0}'.",
+ DescriptionParameters = new object[] { pid },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ else
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2904,
+ DescriptionFormat = "Aggregation action destination contains {0} parameters for avg extended aggregation type. Exactly 4 Parameters are expected.",
+ DescriptionParameters = new object[] { ids.Length },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ else // Single pid expected
+ {
+ string pid = content;
+
+ // Check for 1:200 format
+ if (content.Contains(':'))
+ {
+ pid = content.Split(':')[1];
+ }
+
+ if (ParameterIdSet.Contains(pid)) { continue; }
+
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2903,
+ DescriptionFormat = "Merge action {0} contains non-existing parameter id '{1}'.",
+ DescriptionParameters = new object[] { optionName, pid },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ }
+ break;
+
+ case "limitresult": // Single pid required
+ case "remoteelements":
+ {
+ if (IsInteger(optionContent[1]))
+ {
+ if (ParameterIdSet.Contains(optionContent[1])) { continue; }
+
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2903,
+ DescriptionFormat = "Aggregation action {0} contains non-existing parameter id '{1}'.",
+ DescriptionParameters = new object[] { optionName, optionContent[1] },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ else
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2906,
+ DescriptionFormat = "ID {0} in merge action {1} option is not correctly formatted.",
+ DescriptionParameters = new object[] { optionContent[1], optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ break;
+
+ case "destinationfindpk": // One or more pid's, comma separated
+ {
+ string findpkContent = optionContent[1];
+ string[] findpkPids = findpkContent.Split(',');
+ foreach (string findpkPid in findpkPids)
+ {
+ if (Int32.TryParse(optionContent[1], out int pid))
+ {
+ if (ParameterIdSet.Contains(findpkPid)) { continue; }
+
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2903,
+ DescriptionFormat = "Aggregation action {0} contains non-existing parameter id '{1}'.",
+ DescriptionParameters = new object[] { optionName, pid },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ else
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2906,
+ DescriptionFormat = "ID {0} in merge action {1} option is not correctly formatted.",
+ DescriptionParameters = new object[] { optionContent[1], optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ }
+ break;
+
+ case "defaultvalue":
+ {
+ string[] pidValue = optionContent[1].Split(',');
+ string spid = pidValue[0];
+ if (Int32.TryParse(spid, out int pid))
+ {
+ if (ParameterIdSet.Contains(spid)) { continue; }
+
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2903,
+ DescriptionFormat = "Aggregation action {0} contains non-existing parameter id '{1}'.",
+ DescriptionParameters = new object[] { optionName, pid },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ else
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2906,
+ DescriptionFormat = "ID {0} in merge action {1} option is not correctly formatted.",
+ DescriptionParameters = new object[] { spid, optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ break;
+
+ case "defaultif":
+ {
+ string[] idxValue = optionContent[1].Split(',');
+ string sidx = idxValue[0];
+ if (!Int32.TryParse(sidx, out int idx))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2906,
+ DescriptionFormat = "index {0} in merge action {1} option is not correctly formatted.",
+ DescriptionParameters = new object[] { sidx, optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Checks the chain attributes.
+ ///
+ /// The protocol document.
+ /// List of results.
+ /// The namespace.
+ private void CheckChainAttributes(XmlDocument xDoc, List resultMsg, XmlNamespaceManager xmlNsm)
+ {
+ // Chain options: ??
+
+ // Chain.Field options: options for CPE environment, under construction
+ XmlNodeList xnlFields = xDoc.SelectNodes("slc:Protocol/slc:Chains/slc:Chain/slc:Field[@options]", xmlNsm);
+ foreach (XmlNode xnField in xnlFields)
+ {
+ LineNum = xnField.Attributes?["QA_LNx"].InnerXml;
+ XmlNode xnFieldOptions = xnField.Attributes?["options"];
+ if (xnFieldOptions == null) { continue; }
+
+ string sFieldOptions = xnFieldOptions.InnerXml.ToLower();
+ if (sFieldOptions == String.Empty) { continue; }
+
+ string[] asFieldOptions = sFieldOptions.Split(';');
+ foreach (string sOption in asFieldOptions)
+ {
+ string option = sOption.ToLower();
+ string starter = sOption.Split(':', '=')[0];
+ switch (starter)
+ {
+ // No details needed
+ case "displayinfilter":
+ case "hidediagramalarmcolors":
+ case "ignoreemptyfiltervalues":
+ case "noloadonfilter":
+ case "readonly":
+ case "showcpechilds":
+ case "showsiblings":
+ case "showbubbleupandinstancealarmlevel":
+ case "showtree":
+ case "tilelist":
+ break;
+
+ case "chain":
+ case "chainfilter":
+ case "details":
+ case "detailtabs":
+ case "displayinfiltercombo":
+ case "filter":
+ case "filtercombo":
+ case "maxdiagrampid":
+ case "statustabs":
+ case "taborder":
+ case "tabs":
+ case "topologychains":
+ case "diagramsort":
+ {
+ string[] sOptionSplit = option.Split(':');
+ if (sOptionSplit.Length < 2)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2901,
+ DescriptionFormat = "Unknown or malformed {0} option '{1}'.",
+ DescriptionParameters = new object[] { "Field", sOption },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ }
+ break;
+
+ case "filtermode":
+ {
+ string[] sOptionSplit = option.Split('=');
+ if (sOptionSplit[1] != "edit" && sOptionSplit[1] != "combo")
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2901,
+ DescriptionFormat = "Unknown or malformed {0} option '{1}'.",
+ DescriptionParameters = new object[] { "Field", sOption },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ }
+ break;
+
+ case "fixedposition": // Can be empty or contain value with =
+ {
+ string[] sOptionSplit = option.Split('=');
+ if (sOptionSplit.Length > 2)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2901,
+ DescriptionFormat = "Unknown or malformed {0} option '{1}'.",
+ DescriptionParameters = new object[] { "Field", sOption },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ }
+ break;
+
+ default:
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2901,
+ DescriptionFormat = "Unknown or malformed {0} option '{1}'.",
+ DescriptionParameters = new object[] { "Field", sOption },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ ///
+ /// Checks the pair attributes.
+ ///
+ /// The protocol document.
+ /// List of results.
+ /// The namespace.
+ private void CheckPairAttributes(XmlDocument xDoc, List resultMsg, XmlNamespaceManager xmlNsm)
+ {
+ // Pair options: separator is semicolon, no leading character separator.
+ XmlNodeList xnlPair = xDoc.SelectNodes("slc:Protocol/slc:Pairs/slc:Pair[@options]", xmlNsm);
+ foreach (XmlNode xnPair in xnlPair)
+ {
+ LineNum = xnPair.Attributes?["QA_LNx"].InnerXml;
+ string pairOptions = xnPair.Attributes?["options"]?.InnerXml;
+ if (!String.IsNullOrEmpty(pairOptions))
+ {
+ // This is a semicolon separated list
+ string[] asPairOptions = pairOptions.Split(';');
+
+ // Count connections used in protocol - used when checking connections, retrieved outside loop for performance
+ XmlNodeList xnPortSettings = xDoc.SelectNodes("slc:Protocol/slc:Ports/slc:PortSettings", xmlNsm);
+ int connectionsCount = xnPortSettings.Count;
+ if (xDoc.SelectSingleNode("slc:Protocol/slc:PortSettings", xmlNsm) != null)
+ {
+ connectionsCount++;
+ }
+
+ foreach (string pairOption in asPairOptions)
+ {
+ string[] asOptionContent = pairOption.Split(':');
+ string optionName = asOptionContent[0].ToLower();
+ switch (optionName)
+ {
+ case "onebyte":
+ case "commbreak":
+ case "receiveinterval":
+ case "retries":
+ {
+ // A single numeric value is needed
+ if (Int32.TryParse(asOptionContent[1], out _)) { continue; }
+
+ // Create invalid id error
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2909,
+ DescriptionFormat = "Option {0} value is not correctly formatted. A single integer value is expected.",
+ DescriptionParameters = new object[] { optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ break;
+
+ case "connection":
+ {
+ // A single numeric value is needed
+ if (!Int32.TryParse(asOptionContent[1], out int connectionId)) // Check if value is single number
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2909,
+ DescriptionFormat = "Option {0} value is not correctly formatted. A single integer value is expected.",
+ DescriptionParameters = new object[] { optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+
+ if (connectionId >= connectionsCount)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2910,
+ DescriptionFormat = "Connection number does not match number of ports in PortSettings.",
+ DescriptionParameters = null,
+ TestName = "CheckAtributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ break;
+
+ case "ignoretimeout":
+ {
+ // No further details needed
+ if (asOptionContent.Length > 1) // MichielV: added 16/01/2014. Caused index out of bounds error without this check.
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2911,
+ DescriptionFormat = "Option {0} does not require a value.",
+ DescriptionParameters = new object[] { optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ break;
+
+ default:
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2901,
+ DescriptionFormat = "Unknown or malformed {0} option '{1}'.",
+ DescriptionParameters = new object[] { "Pair", optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Checks the parameter attributes.
+ ///
+ /// The protocol document.
+ /// List of results.
+ /// The namespace.
+ private void CheckParamAttributes(XmlDocument xDoc, List resultMsg, XmlNamespaceManager xmlNsm)
+ {
+ #region Param@options
+
+ // Param options
+ XmlNodeList xnlParam = xDoc.SelectNodes("slc:Protocol/slc:Params/slc:Param[@options]", xmlNsm);
+ foreach (XmlNode xnParam in xnlParam)
+ {
+ LineNum = xnParam.Attributes?["QA_LNx"].InnerXml;
+ XmlNode xnParamOptions = xnParam.Attributes?["options"];
+ if (xnParamOptions == null) { continue; }
+
+ string sParamOptions = xnParamOptions.InnerXml.ToLower();
+ string[] paramOptions = { String.Empty, "snmpset", "snmpsetandgetwithwait", "snmpsetwithwait" };
+ if (!paramOptions.Contains(sParamOptions))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2901,
+ DescriptionFormat = "Unknown or malformed {0} option {1}.",
+ DescriptionParameters = new object[] { "Param", sParamOptions },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ }
+
+ #endregion Param@options
+
+ #region Param.Alarm
+
+ // Param.Alarm options: separator is semicolon. Used with linked tables
+ XmlNodeList xnlParamAlarm = xDoc.SelectNodes("slc:Protocol/slc:Params/slc:Param/slc:Alarm[@*]", xmlNsm);
+ foreach (XmlNode xnParamAlarm in xnlParamAlarm)
+ {
+ LineNum = xnParamAlarm.Attributes?["QA_LNx"].InnerXml;
+ XmlNode xnParamAlarmType = xnParamAlarm.Attributes?["type"];
+ if (xnParamAlarmType != null)
+ {
+ string sType = xnParamAlarmType.InnerXml.ToLower();
+ string nominalId = String.Empty;
+ string multiplier = String.Empty;
+
+ // The type attribute may contain additional normalization settings
+ string[] typeSplit = sType.Split(':');
+ string type = typeSplit[0];
+ if (type != "nominal" && type != "absolute" && type != "relative")
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2912,
+ DescriptionFormat = "Unknown alarm type '{0}'.",
+ DescriptionParameters = new object[] { type },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+
+ if (typeSplit.Length == 2)
+ {
+ // Should be single pid or 2 comma separated pids
+ string sPids = typeSplit[1];
+ if (!Int32.TryParse(sPids, out int iPid))
+ {
+ string[] pids = typeSplit[1].Split(',');
+ if (pids.Length == 2)
+ {
+ nominalId = pids[0];
+ multiplier = pids[1];
+ }
+
+ if (!Int32.TryParse(nominalId, out iPid) || !Int32.TryParse(multiplier, out iPid))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2913,
+ DescriptionFormat = "{0} attribute is not correctly formatted. Expected format is 'valuepid' or 'valuepid,multiplierpid'.",
+ DescriptionParameters = new object[] { "Alarm Type" },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ else if (!ParameterIdSet.Contains(nominalId))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2952,
+ DescriptionFormat = "Parameter with ID {0} not found.",
+ DescriptionParameters = new object[] { nominalId },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ }
+ }
+
+ XmlNode xnActiveTime = xnParamAlarm.Attributes?["activeTime"];
+ if (xnActiveTime != null)
+ {
+ string sActiveTime = xnActiveTime.InnerXml;
+
+ if (!Int32.TryParse(sActiveTime, out int time))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2915,
+ DescriptionFormat = "Alarm activeTime value is not correctly formatted. Expected integer value.",
+ DescriptionParameters = null,
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+
+ XmlNode xnOptions = xnParamAlarm.Attributes?["options"];
+ if (xnOptions != null)
+ {
+ string[] asOptions = xnOptions.InnerXml.Split(';');
+ int properties = 0;
+ foreach (string sOption in asOptions)
+ {
+ char[] colon = { ':' };
+ string[] asOptionContent = sOption.Split(colon, 2); // MVT 25/06/2014, limited to two results to handle colons in the option content.
+ string optionName = asOptionContent[0].ToLower();
+
+ switch (optionName)
+ {
+ case "threshold": // Two parameter id's must be specified, comma separated
+ {
+ string[] tresholdParams = asOptionContent[1].Split(',');
+ if (tresholdParams.All(a => Int32.TryParse(a, out int pid)))
+ {
+ foreach (string pId in tresholdParams)
+ {
+ if (ParameterIdSet.Contains(pId))
+ {
+ continue; // OK
+ }
+
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2952,
+ DescriptionFormat = "Parameter with ID {0} not found.",
+ DescriptionParameters = new object[] { pId },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ else
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2916,
+ DescriptionFormat = "Option {0} is not correctly formatted. Expected format is 2 comma separated Parameter ID's.",
+ DescriptionParameters = new object[] { "treshold" },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ break;
+
+ case "propertynames": // Property labels: one or more names separated by a comma
+ {
+ // This is free text, no check. labels are counted to use in "properties" test.
+ string[] names = asOptionContent[1].Split(',');
+ properties = names.Length;
+ }
+ break;
+
+ case "properties": // Format of the added properties, first character is separator(pipe as default)
+ {
+ char splitter = asOptionContent[1][0];
+ if (splitter != '|') // Pipe character is the default, show a warning if another character is used.
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2917,
+ DescriptionFormat = "Alarm properties are split by first character '{0}'. Use of pipe character '|' is recommended.",
+ DescriptionParameters = new object[] { splitter },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+
+ string sOContent = asOptionContent[1].TrimStart(splitter);
+ string[] asProperties = sOContent.Split(splitter);
+ if (asProperties.Length > properties)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2961,
+ DescriptionFormat = "There are more alarm properties than propertyNames.",
+ DescriptionParameters = null,
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ else if (asProperties.Length < properties)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2962,
+ DescriptionFormat = "There are more propertyNames than alarm properties.",
+ DescriptionParameters = null,
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ }
+ break;
+
+ default:
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2901,
+ DescriptionFormat = "Unknown or malformed {0} option '{1}'.",
+ DescriptionParameters = new object[] { "Alarm", optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ #endregion Param.Alarm
+
+ #region Param.ArrayOptions
+
+ // Param.ArrayOptions SNMP Index: semicolon separated
+
+ // Param.ArrayOptions options: first character = separator
+ XmlNodeList xnlParamArrayOptions = xDoc.SelectNodes("slc:Protocol/slc:Params/slc:Param/slc:ArrayOptions[@options]", xmlNsm);
+ foreach (XmlNode xnArrayOptions in xnlParamArrayOptions)
+ {
+ LineNum = xnArrayOptions.Attributes?["QA_LNx"].InnerXml;
+
+ string optionContent = xnArrayOptions.Attributes?["options"]?.InnerXml;
+ if (String.IsNullOrWhiteSpace(optionContent))
+ {
+ continue;
+ }
+
+ char[] splitter = { ';' };
+ string trimmed = optionContent.Trim(splitter);
+
+ string[] asOptions = trimmed.Split(splitter);
+ foreach (string option in asOptions)
+ {
+ string[] asOption = option.Split('=');
+ string optionName = asOption[0].ToLower();
+ string optionDetails = asOption.Length == 2 ? asOption[1] : String.Empty;
+
+ optionName = optionName.Split(':')[0]; // Quick dirty fix since some options use "=" and some ":" as separator.
+
+ switch (optionName)
+ {
+ case "autoadd":
+ case "customdatabasename":
+ case "database":
+ case "databasename":
+ case "databasenameprotocol":
+ case "directview": // Detailed check to be implemented
+ case "interrupttrend":
+ case "naming": // Detailed check to be implemented
+ case "onlyfiltereddirectview": // Possible check: only use if direct view is already present.
+ case "pkcaseinsensitive": // Not in documentation anymore
+ case "preserve state":
+ case "propertytable":
+ case "processingorder":
+ case "querytablepid":
+ case "resolvedpk": // Not in documentation anymore
+ case "savecolumns": // Not in documentation anymore
+ case "sizehint":
+ case "volatile":
+ case "view": // Detailed check to be implemented
+ break;
+
+ case "discreetdestination":
+ if (!optionDetails.EndsWith(".xml", StringComparison.InvariantCulture))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2966,
+ DescriptionFormat = "DiscreetDestination is not an xml file.",
+ DescriptionParameters = null,
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ break;
+
+ case "filterchange":
+ {
+ string[] filterpairs = optionDetails.Split(',');
+ foreach (string pair in filterpairs)
+ {
+ string[] pids = pair.Split('-');
+ if (pids.Length == 2)
+ {
+ string sLocalPid = pids[0];
+ if (!ParameterIdSet.Contains(sLocalPid))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2968,
+ DescriptionFormat = "Local pid {0} in filterchange pair not found.",
+ DescriptionParameters = new object[] { sLocalPid },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+
+ string foreingPid = pids[1];
+ if (!IsInteger(foreingPid))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2969,
+ DescriptionFormat = "Foreign pid {0} in filterchange pair is not a valid integer number.",
+ DescriptionParameters = new object[] { foreingPid },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ else
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2967,
+ DescriptionFormat = "Filterchange pair {0} is not correctly formatted.",
+ DescriptionParameters = new object[] { pair },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ }
+ break;
+
+ default:
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2901,
+ DescriptionFormat = "Unknown or malformed {0} option '{1}'.",
+ DescriptionParameters = new object[] { "ArrayOptions", optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ break;
+ }
+ }
+ }
+
+ // Param.ArrayOptions.ColumnOptions
+ // Value: comma separated
+ // Options: first character separated
+ XmlNodeList xnlColumnOption = xDoc.SelectNodes(".//slc:ColumnOption[@*]", xmlNsm); // Select all ColumnOptions with attributes
+ foreach (XmlNode xnColumnOption in xnlColumnOption)
+ {
+ LineNum = xnColumnOption.Attributes?["QA_LNx"].InnerXml;
+
+ // Check type
+ XmlNode xnType = xnColumnOption.Attributes?["type"];
+ if (xnType != null)
+ {
+ string sColumnOptionType = xnType.InnerXml.ToLower();
+
+ // Check if option is allowed
+ string[] columnOptionTypes = { "concatenation", "state", "autoincrement", "index", "custom", "retrieved", "snmp", "displaykey", "viewtablekey" };
+ if (!columnOptionTypes.Contains(sColumnOptionType))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2920,
+ DescriptionFormat = "Unknown ColumnOption Type '{0}'",
+ DescriptionParameters = new object[] { sColumnOptionType },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+
+ // Check on retrieved columns in SNMP table (DCP 16252)
+ if (sColumnOptionType == "retrieved")
+ {
+ XmlNode param = xnColumnOption.SelectSingleNode("ancestor::slc:Param", xmlNsm);
+ string id = param?.SelectSingleNode("./slc:Type/@id", xmlNsm)?.InnerXml;
+ if (!String.IsNullOrEmpty(id))
+ {
+ XmlNode xnSnmp = param.SelectSingleNode("./slc:SNMP", xmlNsm);
+ if (xnSnmp != null)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2963,
+ DescriptionFormat = "Retrieved ColumnOption in SNMP table. All columnOptions should be explicitly defined with SNMP type.",
+ DescriptionParameters = new object[] { sColumnOptionType },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ }
+ }
+
+ XmlNode xnValue = xnColumnOption.Attributes?["value"];
+
+ // Number of comma separated list of numbers.
+ string sValues = xnValue?.InnerXml;
+ if (!String.IsNullOrWhiteSpace(sValues))
+ {
+ string[] asValues = sValues.Split(',');
+ foreach (string value in asValues)
+ {
+ if (!Int32.TryParse(value, out int iValue))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2921,
+ DescriptionFormat = "ColumnOption attribute value not correctly formatted. Expected int or comma separated list of int values.",
+ DescriptionParameters = null,
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ }
+
+ XmlNode xnOptions = xnColumnOption.Attributes?["options"];
+ if (xnOptions != null)
+ {
+ string sOptions = xnOptions.InnerXml;
+ if (sOptions != String.Empty)
+ {
+ char[] splitter = { sOptions[0] };
+ if (splitter[0] != ';')
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2922,
+ DescriptionFormat = "ColumnOption options are separated by first character '{0}'. Using semicolon ';' is recommended.",
+ DescriptionParameters = new object[] { splitter[0] },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+
+ string[] asOptions = sOptions.Split(splitter, StringSplitOptions.RemoveEmptyEntries);
+ foreach (string option in asOptions)
+ {
+ string[] asOptionContent = option.Split('=', ':');
+ string optionName = asOptionContent[0].ToLower();
+ switch (optionName)
+ {
+ case "cpedummycolumn":
+ case "delete":
+ case "delta":
+ case "displayicon":
+ case "displayelementalarm":
+ case "displayservicealarm":
+ case "displayviewalarm":
+ case "disableheaderavg":
+ case "disableheadermax":
+ case "disableheadermin":
+ case "disableheadersum":
+ case "disableheatmap":
+ case "disablehistogram":
+ case "dynamicdata":
+ case "enableheaderavg":
+ case "enableheadermax":
+ case "enableheadermin":
+ case "enableheadersum":
+ case "enableheatmap":
+ case "enablehistogram":
+ case "element":
+ case "foreignkey":
+ case "groupby":
+ case "hidden":
+ case "hidekpi":
+ case "hidekpiwhennotinitialized": // Not in documentation anymore
+ case "indexcolumn":
+ case "kpihidewrite":
+ case "kpishowdisplaykey": // Not in documentation anymore
+ case "linkelement":
+ case "rowtextcoloring":
+ case "save":
+ case "selectionsetvar":
+ case "selectionsetcardvar":
+ case "selectionsetpagevar":
+ case "selectionsetworkspacevar":
+ case "separator": // Not in documentation anymore
+ case "setontable":
+ case "severity":
+ case "severitycolumn":
+ case "severitycolumnindex":
+ case "showreadaskpi":
+ case "space":
+ case "subtitle":
+ case "view":
+ case "viewimpact":
+ case "volatile":
+ case "xpos": // Not in documentation anymore
+ case "ypos": // Not in documentation anymore
+ break;
+
+ default:
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2901,
+ DescriptionFormat = "Unknown or malformed {0} option '{1}'.",
+ DescriptionParameters = new object[] { "ColumnOption", optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ #endregion Param.ArrayOptions
+
+ #region Param.CRC.Type
+
+ // Param.CRC.Type options: separator is semicolon.
+ XmlNodeList xnlParamCrcType = xDoc.SelectNodes("/slc:Protocol/slc:Param/slc:CRC/slc:Type[@options]", xmlNsm);
+ foreach (XmlNode paramCrcType in xnlParamCrcType)
+ {
+ LineNum = paramCrcType.Attributes?["QA_LNx"]?.InnerXml;
+ string options = paramCrcType.Attributes?["options"]?.InnerXml?.ToLower();
+ if (String.IsNullOrEmpty(options)) { continue; }
+
+ string[] combinations = { "ones complement", "or totaloffset", "ones complement;or totaloffset", "or totaloffset;ones complement" };
+ if (!combinations.Contains(options))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2923,
+ DescriptionFormat = "Unknown CRC Type option {0}. Expected semicolon separated combination of \"ones complement\" and \"or totaloffset\"",
+ DescriptionParameters = new object[] { options },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+
+ #endregion Param.CRC.Type
+
+ #region Param.Display
+
+ // Param.Display.Parametersview options: pipe-separated list of options.
+ XmlNodeList xnlParametersView = xDoc.SelectNodes("/slc:Protocol/slc:Param/slc:Display/slc:ParametersView", xmlNsm);
+ foreach (XmlNode xnParametersView in xnlParametersView)
+ {
+ LineNum = xnParametersView.Attributes?["QA_LNx"].InnerXml;
+
+ XmlNode xnType = xnParametersView.Attributes?["type"];
+ if (xnType != null)
+ {
+ string sType = xnType.InnerXml;
+ string[] asTypes = { "column", "pie", "row", "stackedarea" };
+ if (!asTypes.Contains(sType.ToLower()))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2924,
+ DescriptionFormat = "Unknown ParametersView type {0}. Expected 'Column', 'Pie', 'Row' or 'StackedArea'.",
+ DescriptionParameters = new object[] { sType },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+
+ XmlNode xnOptions = xnParametersView.Attributes?["options"];
+ if (xnOptions != null)
+ {
+ string sOptions = xnOptions.InnerXml.ToLower();
+ string[] asOptions = sOptions.Split('|');
+ foreach (string optionName in asOptions)
+ {
+ if (!optionName.StartsWith("height=", StringComparison.InvariantCultureIgnoreCase))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2901,
+ DescriptionFormat = "Unknown or malformed {0} option '{1}'.",
+ DescriptionParameters = new object[] { "ParametersView", optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ }
+ }
+ }
+
+ // Param.Display.Parametersview.Parameters.Parameter options: not yet implemented
+
+ #endregion Param.Display
+
+ #region Param.Interprete.Type
+
+ // Param.Interprete.Type (trim)
+ XmlNodeList xnlParamInterpreteType = xDoc.SelectNodes("/slc:Protocol/slc:Param/slc:Interprete/slc:Type[@trim]", xmlNsm);
+ foreach (XmlNode xnParamInterpreteType in xnlParamInterpreteType)
+ {
+ LineNum = xnParamInterpreteType.Attributes?["QA_LNx"].InnerXml;
+ XmlNode xnTrim = xnParamInterpreteType.Attributes?["trim"];
+ if (xnTrim == null) { continue; }
+
+ string sTrim = xnTrim.InnerXml;
+ string[] trims = { "left", "right", "left;right", "right;left" };
+ if (!trims.Contains(sTrim.ToLower()))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2925,
+ DescriptionFormat = "Unknown Interprete type trim {0}. Expected semicolon separated combination of 'left' and 'right'.",
+ DescriptionParameters = new object[] { sTrim },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+
+ #endregion Param.Interprete.Type
+
+ // Param.Measurement.Discreets.Discreet dependency values: semicolon separated list
+ // Param.Measurement.Discreets.Discreet options
+
+ #region Param.Measurement.Type
+
+ // Param.Measurement.Type options
+ XmlNodeList xnlParamMeasurementType = xDoc.SelectNodes("/slc:Protocol/slc:Param/slc:Measurement/slc:Type[@options]", xmlNsm);
+ foreach (XmlNode xnParamMeasurementType in xnlParamMeasurementType)
+ {
+ LineNum = xnParamMeasurementType.Attributes?["QA_LNx"].InnerXml;
+ string type = xnParamMeasurementType.InnerXml;
+ XmlNode xnOptions = xnParamMeasurementType.Attributes?["options"];
+ string sOptions = xnOptions.InnerXml;
+ sOptions = new string(sOptions.Where(c => !Char.IsWhiteSpace(c)).ToArray());
+
+ switch (type.ToLower())
+ {
+ case "number":
+ {
+ // If number: time, time:minute, time:hour
+ string[] asOptionsNumeric = { "time", "time:minute", "time:hour" };
+ if (!asOptionsNumeric.Contains(sOptions.ToLower()))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2926,
+ DescriptionFormat = "Unknown option for Measurement Type number. Possible values are 'time', 'time:minute' or 'time:hour'.",
+ DescriptionParameters = null,
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ break;
+
+ case "string":
+ {
+ // If string: lines, case, options: semicolon separated
+ string[] asCaseString = { "upper", "lower" };
+
+ string lines = xnParamMeasurementType.Attributes?["lines"]?.InnerXml;
+ if (!String.IsNullOrEmpty(lines))
+ {
+ if (!Int32.TryParse(lines, out int iLines))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2927,
+ DescriptionFormat = "Attribute {0} value '{1}' is not formatted correctly. Expected integer number.",
+ DescriptionParameters = new object[] { "Lines", lines },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+
+ string caseAttribute = xnParamMeasurementType.Attributes?["case"]?.InnerXml.ToLower();
+ if (!String.IsNullOrEmpty(caseAttribute) && !asCaseString.Contains(caseAttribute))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2928,
+ DescriptionFormat = "Case value is not correctly formatted. Expected 'upper' or 'lower'.",
+ DescriptionParameters = null,
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+
+ // Options tag
+ string[] asOptions = sOptions.Split(';');
+ foreach (string option in asOptions)
+ {
+ string[] asContent = option.Split('=');
+ string optionName = asContent[0].ToLower();
+ switch (optionName)
+ {
+ case "hscroll":
+ case "fixedfont":
+ case "password":
+ break;
+
+ case "number":
+ {
+ string value = asContent[1];
+ string[] checkValues = { "true", "false" };
+ if (!checkValues.Contains(value))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2929,
+ DescriptionFormat = "Number value is not correctly formatted. Expected \"true\" or \"false\".",
+ DescriptionParameters = null,
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ break;
+
+ case "tab":
+ {
+ string sValues = asContent[1];
+ if (!String.IsNullOrEmpty(sValues))
+ {
+ string[] asValues = sValues.Split(',');
+ foreach (string value in asValues)
+ {
+ if (Int32.TryParse(value, out int iValue)) { continue; }
+
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2930,
+ DescriptionFormat = "Tab option value not correctly formatted. Expected int or comma separated list of int values.",
+ DescriptionParameters = null,
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ }
+ break;
+
+ default:
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2931,
+ DescriptionFormat = "Unknown option {0} for Measurement Type String.",
+ DescriptionParameters = new object[] { optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ break;
+ }
+ }
+ }
+ break;
+
+ case "title":
+ {
+ // If title: options: semicolon separated
+ string[] asCheckValues = { "begin", "end", "end;connect", "begin;connect" };
+ if (!asCheckValues.Contains(sOptions.ToLower()))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2901,
+ DescriptionFormat = "Unknown or malformed {0} option '{1}'.",
+ DescriptionParameters = new object[] { "Measurement Type", sOptions },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ }
+ break;
+
+ case "analog":
+ {
+ // If analog: options: hscroll
+ if (!String.Equals(sOptions, "hscroll", StringComparison.OrdinalIgnoreCase))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2901,
+ DescriptionFormat = "Unknown or malformed {0} option '{1}'.",
+ DescriptionParameters = new object[] { "ArrayOptions", sOptions },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ }
+ break;
+
+ case "table":
+ {
+ string[] asOptions = sOptions.Split('=');
+ string optionName = asOptions[0].ToLower();
+ string optionValue = asOptions[1].ToLower();
+
+ // Get columns in table definition
+ string tablePid = xnParamMeasurementType.ParentNode.ParentNode.Attributes?["id"].InnerXml;
+ Dictionary columnPids = GetColumnPids(xDoc, tablePid);
+ int displayedColumns = 0;
+
+ if (optionName == "tab")
+ {
+ // Correct option for Type table, continue test
+ string[] asContent = optionValue.Split(',');
+ foreach (string content in asContent)
+ {
+ string[] asInternal = content.Split(':');
+ string internalName = asInternal[0];
+ string internalValue = String.Empty;
+ if (asInternal.Length == 2)
+ {
+ internalValue = asInternal[1];
+ }
+
+ switch (internalName)
+ {
+ case "columns":
+ {
+ string[] asColumns = internalValue.Split('-');
+ displayedColumns = asColumns.Length;
+
+ foreach (string sColumn in asColumns)
+ {
+ string trimColumn = sColumn.Trim();
+ string[] asColumnDetails = trimColumn.Split('|');
+ string columnPID = asColumnDetails[0];
+ string columnIndex = String.Empty;
+ if (asColumnDetails.Length == 2)
+ {
+ columnIndex = asColumnDetails[1];
+ }
+
+ // Check that values are numbers. This will also cause an error if illegal separators have been used.
+ if (!Int32.TryParse(columnPID, out int iColumnPid))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2932,
+ DescriptionFormat = "Column Parameter ID {0} is not correctly formatted. Expected integer number.",
+ DescriptionParameters = new object[] { columnPID },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ else
+ {
+ if (!ParameterIdSet.Contains(columnPID))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2952,
+ DescriptionFormat = "Parameter with ID {0} not found.",
+ DescriptionParameters = new object[] { columnPID },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+
+ if (columnIndex != String.Empty)
+ {
+ if (!Int32.TryParse(columnIndex, out int iColumnIndex))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2933,
+ DescriptionFormat = "Column Index {0} is not correctly formatted. Expected integer number.",
+ DescriptionParameters = new object[] { columnIndex },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+
+ // Check that index - pid combination is correct, and that they are in table definition.
+ if (columnPids.Keys.Contains(columnPID))
+ {
+ string index = String.Empty;
+ if (columnIndex != index)
+ {
+ // Pid-index combination is incorrect, generate an error.
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2934,
+ DescriptionFormat = "Combination of Parameter ID {0} with table index {1} is incorrect. Check table definition.",
+ DescriptionParameters = new object[] { columnPID, columnIndex },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ else
+ {
+ // Column pid is in column order, but not in table definition, generate an error.
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2935,
+ DescriptionFormat = "Parameter ID {0} not found in table definition.",
+ DescriptionParameters = new object[] { columnPID },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ }
+ }
+ break;
+
+ case "lines":
+ {
+ if (!Int32.TryParse(internalValue, out int iLines))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2927,
+ DescriptionFormat = "Attribute {0} value '{1}' is not formatted correctly. Expected integer number.",
+ DescriptionParameters = new object[] { "Lines", optionValue },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ break;
+
+ case "width":
+ {
+ string[] asWidth = internalValue.Split('-');
+ int iWidths = asWidth.Length;
+
+ // Check that length equals number of columns
+ if (iWidths != displayedColumns)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2936,
+ DescriptionFormat = "The number of width items does not match the number of columns items.",
+ DescriptionParameters = null,
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+
+ // Check that all values are numeric
+ foreach (string sWidth in asWidth)
+ {
+ if (!Int32.TryParse(sWidth, out int iWidth))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2927,
+ DescriptionFormat = "Attribute {0} value '{1}' is not formatted correctly. Expected integer number.",
+ DescriptionParameters = new object[] { "Width", sWidth },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ }
+ break;
+
+ case "sort":
+ {
+ string[] asSort = internalValue.Split('-');
+
+ // Check that number of sort statements is less or equal than number of columns.
+ if (asSort.Length > displayedColumns)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2937,
+ DescriptionFormat = "The number of sort statements is larger than the number of columns.",
+ DescriptionParameters = null,
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+
+ // Check that all values are "int" or "string".
+ foreach (string sort in asSort)
+ {
+ string sLowerCaseSort = sort.ToLower();
+ string[] asSortTypes = { "int", "string", "ip" };
+ string[] asSortOrders = { "asc", "desc" };
+ if (!asSortTypes.Contains(sLowerCaseSort))
+ {
+ string[] asSortContent = sLowerCaseSort.Split('|');
+ if (asSortContent.Length >= 2 && asSortContent.Length < 4) // Order is included
+ {
+ string sortOrder = asSortContent[1];
+ if (!asSortOrders.Contains(sortOrder))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2938,
+ DescriptionFormat = "Unknown or malformed sort statement '{0}'.",
+ DescriptionParameters = new object[] { sort },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+
+ if (asSortContent.Length == 3)
+ {
+ string sortdetail = asSortContent[2];
+ if (!Int32.TryParse(sortdetail, out int detail))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2938,
+ DescriptionFormat = "Unknown or malformed sort statement '{0}'.",
+ DescriptionParameters = new object[] { sort },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ }
+ }
+ else
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2938,
+ DescriptionFormat = "Unknown or malformed sort statement '{0}'.",
+ DescriptionParameters = new object[] { sort },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ }
+ }
+ }
+ break;
+
+ case "filter":
+ {
+ string[] asFilterValues = { "true", "false" };
+ if (!asFilterValues.Contains(internalValue.ToLower()))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2939,
+ DescriptionFormat = "Unknown or malformed filter value '{0}'. Expected 'true' or 'false'.",
+ DescriptionParameters = new object[] { internalValue },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ }
+ break;
+
+ default:
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2901,
+ DescriptionFormat = "Unknown or malformed {0} option '{1}'.",
+ DescriptionParameters = new object[] { "Measurement tab", internalName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ break;
+ }
+ }
+ }
+ else // Unknown option for type table
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2940,
+ DescriptionFormat = "Unknown or malformed Measurement Type option {0} for Type table.",
+ DescriptionParameters = new object[] { optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ }
+ break;
+ }
+ }
+
+ #endregion Param.Measurement.Type
+
+ #region Param.SNMP.OID
+
+ // Param.SNMP.OID options: semicolon separated
+ XmlNodeList xnlParamSnmpOid = xDoc.SelectNodes("/slc:Protocol/slc:Params/slc:Param/slc:SNMP/slc:OID[@options]", xmlNsm);
+ foreach (XmlNode xnParamSnmpOid in xnlParamSnmpOid)
+ {
+ LineNum = xnParamSnmpOid.Attributes?["QA_LNx"].InnerXml;
+ XmlNode xnOptions = xnParamSnmpOid.Attributes?["options"];
+ if (xnOptions != null)
+ {
+ string sOptions = xnOptions.InnerXml.ToLower();
+ if (sOptions != String.Empty)
+ {
+ string trimmed = sOptions.Trim(';');
+ string[] asOptions = trimmed.Split(';');
+ foreach (string option in asOptions)
+ {
+ string[] asContent = option.Split(':');
+ string optionName = asContent[0];
+ switch (optionName)
+ {
+ case "column":
+ case "instance":
+ case "multiplegetnext":
+ case "subtable":
+ case "partialsnmp":
+ break;
+
+ case "bulk":
+ case "multipleget":
+ case "multiplegetbulk":
+ {
+ if (asContent.Length > 1) // Bulk options can be used without value specified.
+ {
+ if (!Int32.TryParse(asContent[1], out _))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2947,
+ DescriptionFormat = "Option {0} value '{1}' is not formatted correctly, expected integer number.",
+ DescriptionParameters = new object[] { optionName, asContent[1] },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ }
+ break;
+
+ default:
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2901,
+ DescriptionFormat = "Unknown or malformed {0} option '{1}'.",
+ DescriptionParameters = new object[] { "SNMP OID", optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ #endregion Param.SNMP.OID
+
+ // Param.SNMP.TrapOid
+
+ #region Param.Type
+
+ // Param.Type
+ // Distribution: values with colons
+ XmlNodeList xnlParamTypeDistribution = xDoc.SelectNodes("/slc:Protocol/slc:Params/slc:Param/slc:Type[@distribution]", xmlNsm);
+ foreach (XmlNode xnType in xnlParamTypeDistribution)
+ {
+ LineNum = xnType.Attributes?["QA_LNx"].InnerXml;
+ XmlNode xnDistribution = xnType.Attributes?["distribution"];
+ string sDistribution = xnDistribution.InnerXml;
+ string[] asDistribution = sDistribution.Split(';');
+
+ foreach (string distribution in asDistribution)
+ {
+ string[] asContent = distribution.Split(':');
+ string distributionName = asContent[0];
+ switch (distributionName.ToLower())
+ {
+ case "protocol":
+ case "version":
+ case "pollingip":
+ case "pollingipport":
+ case "busaddress":
+ case "pid":
+ case "remote":
+ break;
+
+ default:
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2948,
+ DescriptionFormat = "Unknown setting {0} in Param Type distribution attribute.",
+ DescriptionParameters = new object[] { distributionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ break;
+ }
+ }
+ }
+
+ // Options:semicolon separated
+ XmlNodeList xnlParamTypeOptions = xDoc.SelectNodes("/slc:Protocol/slc:Params/slc:Param/slc:Type[@options]", xmlNsm);
+ foreach (XmlNode xnType in xnlParamTypeOptions)
+ {
+ LineNum = xnType.Attributes?["QA_LNx"].InnerXml;
+ XmlNode xnOptions = xnType.Attributes?["options"];
+ string sOptions = xnOptions.InnerXml;
+ string[] asOptions = sOptions.Split(';');
+ foreach (string option in asOptions)
+ {
+ string[] asContent = option.Split('=');
+ string optionName = asContent[0];
+ switch (optionName.ToLower())
+ {
+ case "dynamic snmp get":
+ case "linkalarmvalue":
+ case "ssh username":
+ case "ssh pwd":
+ case "ssh options":
+ break;
+
+ case "headertrailerlink":
+ // Covered already in new validator
+ break;
+
+ case "loadoid":
+ {
+ if (String.Equals(xnType.InnerXml, "array", StringComparison.OrdinalIgnoreCase))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2964,
+ DescriptionFormat = "loadOID option is not allowed on table parameters.",
+ DescriptionParameters = null,
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ break;
+
+ case "connection":
+ {
+ if (!Int32.TryParse(asContent[1], out int iConnection))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2947,
+ DescriptionFormat = "Option {0} value '{1}' is not formatted correctly, expected integer number.",
+ DescriptionParameters = new object[] { optionName, asContent[1] },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ break;
+
+ case "dimensions":
+ break;
+
+ case "columntypes":
+ break;
+
+ default:
+ {
+ // Filter for dynamic ip
+ string regex = @"dynamic\x20(ip\x20\d*)?";
+ if (!Regex.IsMatch(optionName, regex))
+ {
+ // If no match, create unknown error.
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2901,
+ DescriptionFormat = "Unknown or malformed {0} option '{1}'.",
+ DescriptionParameters = new object[] { "Parameter Type", optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ #endregion Param.Type
+ }
+
+ ///
+ /// Checks the QAction attributes.
+ ///
+ /// The protocol document.
+ /// List of results.
+ /// The namespace.
+ private void CheckQActionAttributes(XmlDocument xDoc, List resultMsg, XmlNamespaceManager xmlNsm)
+ {
+ // QAction dllImport: semicolon separated -- check by XSD restriction? ([^\/:*?"<>|;]+\.dll)(;[^\/:*?"<>|;]+\.dll)* (in xsd: ([^\/:*?"><|;]+\.dll)(;[^\/:*?"><|;]+\.dll)*)
+ // Get all QAction id's
+ HashSet qActionIds = new HashSet();
+ XmlNodeList xnlQAction = xDoc.SelectNodes("/slc:Protocol/slc:QActions/slc:QAction", xmlNsm);
+ foreach (XmlNode xnQaction in xnlQAction)
+ {
+ XmlNode xnQaId = xnQaction?.Attributes?.GetNamedItem("id");
+ if (xnQaId == null)
+ {
+ continue; // No error generated as this should be enforced by XSD
+ }
+
+ qActionIds.Add(xnQaId.InnerXml);
+ }
+
+ XmlNodeList xnlQActionDll = xDoc.SelectNodes("/slc:Protocol/slc:QActions/slc:QAction[@dllImport]", xmlNsm);
+ foreach (XmlNode xnQAction in xnlQActionDll)
+ {
+ LineNum = xnQAction.Attributes?["QA_LNx"].InnerXml;
+ XmlNode xnDll = xnQAction.Attributes?["dllImport"];
+
+ if (xnDll == null) { continue; }
+
+ string sDll = xnDll.InnerXml;
+ List allDll = sDll.Split(';').ToList();
+ foreach (string dll in allDll)
+ {
+ const string ProtocolString = "[ProtocolName].[ProtocolVersion].QAction.";
+ if (dll.StartsWith(ProtocolString, StringComparison.InvariantCulture))
+ {
+ string dllid = dll.Substring(ProtocolString.Length, dll.LastIndexOf('.') - ProtocolString.Length);
+ if (qActionIds.Contains(dllid)) { continue; }
+
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2972,
+ DescriptionFormat = "Non existing QAction dll reference {0}.",
+ DescriptionParameters = new object[] { dll },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ }
+
+ // QAction inputParameters
+ XmlNodeList xnlQActionInputParameters = xDoc.SelectNodes("/slc:Protocol/slc:QActions/slc:QAction[@inputParameters]", xmlNsm);
+ foreach (XmlNode xnQAction in xnlQActionInputParameters)
+ {
+ LineNum = xnQAction.Attributes?["QA_LNx"].InnerXml;
+ XmlNode xnQActionInputParameter = xnQAction.Attributes?["inputParameters"];
+ XmlNode xnQActionId = xnQAction.Attributes?["id"];
+ string sQActionId = xnQActionId?.InnerXml;
+ if (xnQActionInputParameter == null) { continue; }
+
+ string sInputParams = xnQActionInputParameter.InnerXml;
+ List lInputParams = sInputParams.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries).ToList();
+
+ foreach (string inputParam in lInputParams)
+ {
+ int inputParamPid;
+ if (!Int32.TryParse(inputParam, out inputParamPid))
+ {
+ continue;
+ }
+
+ // Is a parameter inside the protocol
+ if (!ParameterInfoDictionary.TryGetValue(inputParamPid, out ParameterInfo parameterInfo))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2971,
+ DescriptionFormat = "InputParameter parameter {0} does not exist.",
+ DescriptionParameters = new object[] { inputParam },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ else
+ {
+ // Check if it's a table
+ if (String.Equals(parameterInfo.Type, "array", StringComparison.OrdinalIgnoreCase))
+ {
+ // Check if it has an Interprete
+ if (String.IsNullOrWhiteSpace(parameterInfo.IntRawType) || String.IsNullOrWhiteSpace(parameterInfo.IntType))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(parameterInfo.LineNum),
+ ErrorId = 2973,
+ DescriptionFormat = "Table ({0}), that is being used as inputParameter on QAction {1}, has no valid Interprete tag defined.",
+ DescriptionParameters = new object[] { parameterInfo.Pid, sQActionId },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Critical // This can have a lot of impact on the system.
+ });
+ }
+ else
+ {
+ // Check if it's the correct Interprete
+ if (!String.Equals(parameterInfo.IntRawType, "other", StringComparison.OrdinalIgnoreCase) || !String.Equals(parameterInfo.IntType, "double", StringComparison.OrdinalIgnoreCase))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(parameterInfo.LineNum),
+ ErrorId = 2974,
+ DescriptionFormat = "Table ({0}), that is being used as inputParameter on QAction {1}, has an incorrect Interprete tag defined. (Should be 'other'-'double')",
+ DescriptionParameters = new object[] { parameterInfo.Pid, sQActionId },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Critical // This can have a lot of impact on the system.
+ });
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // QAction options
+ XmlNodeList xnlQactionOptions = xDoc.SelectNodes("/slc:Protocol/slc:QActions/slc:QAction[@options]", xmlNsm);
+ foreach (XmlNode xnQAction in xnlQactionOptions)
+ {
+ LineNum = xnQAction.Attributes?["QA_LNx"].InnerXml;
+ XmlNode options = xnQAction.Attributes?["options"];
+ if (options == null) { continue; }
+
+ string sOptions = options.InnerXml;
+ string[] asOptions = sOptions.Split(';');
+ foreach (string option in asOptions)
+ {
+ string[] asContent = option.Split('=');
+ string optionName = asContent[0];
+ switch (optionName.ToLower())
+ {
+ case "binary":
+ case "debug":
+ case "group":
+ case "precompile":
+ case "queued":
+ case "dllname":
+ case "":
+ break;
+ default:
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2901,
+ DescriptionFormat = "Unknown or malformed {0} option '{1}'.",
+ DescriptionParameters = new object[] { "QAction", optionName },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Minor
+ });
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Checks the relation attributes.
+ ///
+ /// The protocol document.
+ /// List of results.
+ /// The namespace.
+ private void CheckRelationAttributes(XmlDocument xDoc, List resultMsg, XmlNamespaceManager xmlNsm)
+ {
+ // Relations.Relation options
+ XmlNodeList xnlRelationOptions = xDoc.SelectNodes("/slc:Protocol/slc:Relations/slc:Relation[@options]", xmlNsm);
+ List topologyNames = new List();
+ foreach (XmlNode xnRelation in xnlRelationOptions)
+ {
+ LineNum = xnRelation.Attributes?["QA_LNx"].InnerXml;
+ string sOption = xnRelation.Attributes?["options"].InnerXml.ToLower();
+ if (String.IsNullOrWhiteSpace(sOption)) { continue; }
+
+ string[] asOptions = sOption.Split(':');
+ if (asOptions[0] != "includeinalarms")
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2953,
+ DescriptionFormat = "The first part in the relation option must be 'includeinalarms'.",
+ DescriptionParameters = null,
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+
+ if (asOptions.Length == 2)
+ {
+ if (topologyNames.Contains(asOptions[1]))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2954,
+ DescriptionFormat = "Topology name \"{0}\" is not unique.",
+ DescriptionParameters = new object[] { asOptions[1] },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ else
+ {
+ topologyNames.Add(asOptions[1]);
+ }
+ }
+
+ if (asOptions.Length == 3 && !String.Equals(asOptions[2], "righttoplevel", StringComparison.OrdinalIgnoreCase))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2955,
+ DescriptionFormat = "The third part in the relation option must be 'righttoplevel'.",
+ DescriptionParameters = null,
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ }
+
+ ///
+ /// Checks the response attributes.
+ ///
+ /// The protocol document.
+ /// List of results.
+ /// The namespace.
+ private void CheckResponseAttributes(XmlDocument xDoc, List resultMsg, XmlNamespaceManager xmlNsm)
+ {
+ // Response options
+ XmlNodeList xnlResponseOption = xDoc.SelectNodes("/slc:Protocol/slc:Responses/slc:Response[@options]", xmlNsm);
+ foreach (XmlNode xnResponse in xnlResponseOption)
+ {
+ LineNum = xnResponse.Attributes?["QA_LNx"].InnerXml;
+ string sOptions = xnResponse.Attributes?["options"]?.InnerXml;
+ string sRegex = @"connection:\d+";
+ if (sOptions != null && !Regex.IsMatch(sOptions.ToLower(), sRegex))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2956,
+ DescriptionFormat = "Response option {0} does not match format 'connection:XX'.",
+ DescriptionParameters = new object[] { sOptions },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+
+ // Response.Content optional
+ XmlNodeList xnlResponseContentOptional = xDoc.SelectNodes(".//slc:Responses/slc:Response/slc:Content[@optional]", xmlNsm);
+ foreach (XmlNode xnContent in xnlResponseContentOptional)
+ {
+ LineNum = xnContent.Attributes?["QA_LNx"]?.InnerXml;
+ string sOptional = xnContent.Attributes?["optional"]?.InnerXml;
+ string[] asOptional = sOptional?.Split(';');
+ string sRegex = @"\d+\++|\d+\*?";
+ int lastnr = -1;
+ if (asOptional == null) { continue; }
+
+ foreach (string optional in asOptional)
+ {
+ if (!Regex.IsMatch(optional, sRegex))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2957,
+ DescriptionFormat = "Optional response definition {0} is not correctly formatted.",
+ DescriptionParameters = new object[] { optional },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+
+ string sNumber = optional.Trim('*', '+');
+ if (Int32.TryParse(sNumber, out int newnr))
+ {
+ // Check if number is larger than previous
+ if (newnr <= lastnr)
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2958,
+ DescriptionFormat = "The order of optional responses is incorrect.{0} comes after {1}.",
+ DescriptionParameters = new object[] { newnr, lastnr },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+ }
+ else
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 2959,
+ DescriptionFormat = "Invalid substring '{0}' in optional response definition.",
+ DescriptionParameters = new object[] { sNumber },
+ TestName = "CheckAttributesContent",
+ Severity = Severity.Major
+ });
+ }
+
+ lastnr = newnr;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol/Legacy/ProtocolValidator.cs b/Protocol/Legacy/ProtocolValidator.cs
new file mode 100644
index 00000000..193d7eec
--- /dev/null
+++ b/Protocol/Legacy/ProtocolValidator.cs
@@ -0,0 +1,382 @@
+//#define DebugValidator
+namespace Skyline.DataMiner.ProtocolValidator
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Threading;
+ using System.Xml;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Data;
+ using Skyline.DataMiner.CICD.Validators.Common.Interfaces;
+ using Skyline.DataMiner.CICD.Validators.Common.Model;
+
+ using XmlEdit = Skyline.DataMiner.CICD.Parsers.Common.XmlEdit;
+
+ ///
+ /// Methods for validating a DataMiner Protocol.
+ ///
+ public class Validator : IValidator
+ {
+ ///
+ /// Runs the validator tests
+ ///
+ /// List of results.
+ public IList RunValidate(IProtocolInputData input, ValidatorSettings validatorSettings, CancellationToken cancellationToken)
+ {
+ var results = new List();
+
+ // validate main protocol
+ results.AddRange(ValidateProtocol(input));
+
+ // validate exported protocols, if any
+ foreach (var ep in input.Model.GetAllExportedProtocols())
+ {
+ var exportInput = new ProtocolInputData(ep.Model, ep.Document, null);
+ var exportResults = ValidateProtocol(exportInput);
+
+ // indicate for which DVE these results were created
+ foreach (IValidationResult exportResult in exportResults)
+ {
+ exportResult.WithDveExport(ep.TablePid, ep.Name);
+ }
+
+ results.AddRange(exportResults);
+ }
+
+ return results;
+ }
+
+ private IList ValidateProtocol(IProtocolInputData input)
+ {
+ var validationResult = new List();
+
+ var protocolChecks = new ProtocolChecks();
+
+ // Add line numbers to XML file
+ bool linesLoaded;
+ XmlDocument xDoc;
+ try
+ {
+ xDoc = protocolChecks.LoadWithLineNums(input.Document.GetXml());
+ linesLoaded = true;
+ }
+ catch (Exception ex)
+ {
+ xDoc = null;
+ linesLoaded = false;
+
+ validationResult.Add(new InternalError
+ {
+ Line = Convert.ToInt32(protocolChecks.LineNum),
+ TestName = "LoadLineNums",
+ DescriptionParameters = new object[] { "loading line numbers", ex.ToString() },
+ });
+ }
+
+ // Run tests
+ if (linesLoaded)
+ {
+ try
+ {
+ protocolChecks.CollectParameterInfo(xDoc);
+ }
+ catch (Exception ex)
+ {
+ validationResult.Add(new InternalError
+ {
+ Line = Convert.ToInt32(protocolChecks.LineNum),
+ DescriptionParameters = new object[] { "Collecting Parameter Info", ex.ToString() },
+ TestName = "collectParameterInfo",
+ });
+ }
+
+ try
+ {
+ protocolChecks.GetDuplicatedParams(xDoc);
+ }
+ catch (Exception ex)
+ {
+ validationResult.Add(new InternalError
+ {
+ Line = Convert.ToInt32(protocolChecks.LineNum),
+ DescriptionParameters = new object[] { "Get duplicated parameters.", ex.ToString() },
+ TestName = "getDuplicatedParams"
+ });
+ }
+#if DebugValidator
+ sw.Stop();
+ log.WriteLog("collect parameter info done in " + sw.Elapsed.ToString());
+ sw.Reset();
+ sw.Start();
+#endif
+
+ try
+ {
+ validationResult.AddRange(protocolChecks.CheckTableColumnParams(xDoc));
+ }
+ catch (Exception ex)
+ {
+ validationResult.Add(new InternalError
+ {
+ Line = Convert.ToInt32(protocolChecks.LineNum),
+ DescriptionParameters = new object[] { "Check Table Column Parameters", ex.ToString() },
+ TestName = "CheckTableColumnParams"
+ });
+ }
+
+ try
+ {
+ validationResult.AddRange(protocolChecks.CheckPortSettings(xDoc));
+ }
+ catch (Exception ex)
+ {
+ validationResult.Add(new InternalError
+ {
+ Line = Convert.ToInt32(protocolChecks.LineNum),
+ DescriptionParameters = new object[] { "Check Port Settings", ex.ToString() },
+ TestName = "CheckPortSettings"
+ });
+ }
+
+ try
+ {
+ validationResult.AddRange(protocolChecks.CheckGroupSettings(xDoc));
+ }
+ catch (Exception ex)
+ {
+ validationResult.Add(new InternalError
+ {
+ Line = Convert.ToInt32(protocolChecks.LineNum),
+ DescriptionParameters = new object[] { "Check Group Settings", ex.ToString() },
+ TestName = "CheckGroupSettings"
+ });
+ }
+
+ try
+ {
+ if (input.Model.MainProtocolModel == null)
+ {
+ validationResult.AddRange(protocolChecks.CheckDveColumnOptionElement(xDoc));
+ }
+ }
+ catch (Exception ex)
+ {
+ validationResult.Add(new InternalError
+ {
+ Line = Convert.ToInt32(protocolChecks.LineNum),
+ DescriptionParameters = new object[] { "Check DVE Column Option Element", ex.ToString() },
+ TestName = "CheckDVEColumnOptionElement"
+ });
+ }
+
+#if DebugValidator
+ sw.Stop();
+ log.WriteLog("CheckDVEColumnOptionElement done in " + sw.Elapsed.ToString());
+ sw.Reset();
+ sw.Start();
+#endif
+
+ try
+ {
+ validationResult.AddRange(protocolChecks.CheckPositions(xDoc));
+ }
+ catch (Exception ex)
+ {
+ validationResult.Add(new InternalError
+ {
+ Line = Convert.ToInt32(protocolChecks.LineNum),
+ DescriptionParameters = new object[] { "Check Positions", ex.ToString() },
+ TestName = "CheckPositions"
+ });
+ }
+
+ try
+ {
+ validationResult.AddRange(protocolChecks.CheckTrendAlarm(xDoc));
+ }
+ catch (Exception ex)
+ {
+ validationResult.Add(new InternalError
+ {
+ Line = Convert.ToInt32(protocolChecks.LineNum),
+ DescriptionParameters = new object[] { "Check Trend Alarm", ex.ToString() },
+ TestName = "CheckTrendAlarm"
+ });
+ }
+
+ try
+ {
+ validationResult.AddRange(protocolChecks.CheckRawTypeDouble(xDoc));
+ }
+ catch (Exception ex)
+ {
+ validationResult.Add(new InternalError
+ {
+ Line = Convert.ToInt32(protocolChecks.LineNum),
+ DescriptionParameters = new object[] { "Check Raw Type Double", ex.ToString() },
+ TestName = "CheckRawTypeDouble"
+ });
+ }
+
+ try
+ {
+ validationResult.AddRange(protocolChecks.CheckAttributesContent(xDoc));
+ }
+ catch (Exception ex)
+ {
+ validationResult.Add(new InternalError
+ {
+ Line = Convert.ToInt32(protocolChecks.LineNum),
+ DescriptionParameters = new object[] { "Check Attributes Content", ex.ToString() },
+ TestName = "CheckAttributesContent"
+ });
+ }
+
+ try
+ {
+ validationResult.AddRange(protocolChecks.CheckCopyAction(xDoc));
+ }
+ catch (Exception ex)
+ {
+ validationResult.Add(new InternalError
+ {
+ Line = Convert.ToInt32(protocolChecks.LineNum),
+ DescriptionParameters = new object[] { "Check Copy Action", ex.ToString() },
+ TestName = "CheckCopyAction"
+ });
+ }
+
+ try
+ {
+ validationResult.AddRange(protocolChecks.CheckTimers(xDoc));
+ }
+ catch (Exception ex)
+ {
+ validationResult.Add(new InternalError
+ {
+ Line = Convert.ToInt32(protocolChecks.LineNum),
+ DescriptionParameters = new object[] { "Check Timers", ex.ToString() },
+ TestName = "CheckTimers"
+ });
+ }
+
+ try
+ {
+ validationResult.AddRange(protocolChecks.CheckRecursivePageButtons(xDoc));
+ }
+ catch (Exception ex)
+ {
+ validationResult.Add(new InternalError
+ {
+ Line = Convert.ToInt32(protocolChecks.LineNum),
+ DescriptionParameters = new object[] { "Check Recursive Page Buttons", ex.ToString() },
+ TestName = "CheckRecursivePageButtons"
+ });
+ }
+
+ try
+ {
+ validationResult.AddRange(protocolChecks.CheckTableIndexSequence(xDoc));
+ }
+ catch (Exception ex)
+ {
+ validationResult.Add(new InternalError
+ {
+ Line = Convert.ToInt32(protocolChecks.LineNum),
+ DescriptionParameters = new object[] { "Check Table Index Sequence", ex.ToString() },
+ TestName = "CheckTableIndexSequence"
+ });
+ }
+
+#if DebugValidator
+ sw.Stop();
+ log.WriteLog("CheckTableIndexSequence done in " + sw.Elapsed.ToString());
+ sw.Reset();
+ sw.Start();
+#endif
+
+ try
+ {
+ validationResult.AddRange(protocolChecks.CheckRTDisplayTrue(xDoc));
+ }
+ catch (Exception ex)
+ {
+ validationResult.Add(new InternalError
+ {
+ Line = Convert.ToInt32(protocolChecks.LineNum),
+ DescriptionParameters = new object[] { "Check RTDisplay", ex.ToString() },
+ TestName = "CheckRTDisplayTrue"
+ });
+ }
+
+ try
+ {
+ validationResult.AddRange(protocolChecks.CheckResponseContent(xDoc));
+ }
+ catch (Exception ex)
+ {
+ validationResult.Add(new InternalError
+ {
+ Line = Convert.ToInt32(protocolChecks.LineNum),
+ DescriptionParameters = new object[] { "Check Response Content", ex.ToString() },
+ TestName = "CheckResponseContent"
+ });
+ }
+
+ try
+ {
+ validationResult.AddRange(protocolChecks.CheckTableColumnExports(xDoc));
+ }
+ catch (Exception ex)
+ {
+ validationResult.Add(new InternalError
+ {
+ Line = Convert.ToInt32(protocolChecks.LineNum),
+ DescriptionParameters = new object[] { "Check Table Column Exports", ex.ToString() },
+ TestName = "CheckTableColumnExports"
+ });
+ }
+
+ try
+ {
+ validationResult.AddRange(protocolChecks.CheckProtocolNames(xDoc));
+ }
+ catch (Exception ex)
+ {
+ validationResult.Add(new InternalError
+ {
+ Line = Convert.ToInt32(protocolChecks.LineNum),
+ DescriptionParameters = new object[] { "Check Protocol Names", ex.ToString() },
+ TestName = "CheckProtocolNames"
+ });
+ }
+
+ try
+ {
+ validationResult.AddRange(protocolChecks.CheckInterpreteMeasurement(xDoc));
+ }
+ catch (Exception ex)
+ {
+ validationResult.Add(new InternalError
+ {
+ Line = Convert.ToInt32(protocolChecks.LineNum),
+ DescriptionParameters = new object[] { "Check Interprete Measurement", ex.ToString() },
+ TestName = "CheckProtocolNames"
+ });
+ }
+ }
+
+ return validationResult;
+ }
+
+ public ICodeFixResult ExecuteCodeFix(XmlEdit.XmlDocument document, Skyline.DataMiner.CICD.Models.Protocol.Edit.Protocol protocol, IValidationResult result, ValidatorSettings validatorSettings)
+ {
+ throw new NotSupportedException();
+ }
+
+ public IList RunCompare(IProtocolInputData newCode, IProtocolInputData previousCode, ValidatorSettings validatorSettings, CancellationToken cancellationToken)
+ {
+ throw new NotSupportedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol/Legacy/ValidationResult.cs b/Protocol/Legacy/ValidationResult.cs
new file mode 100644
index 00000000..46a520d6
--- /dev/null
+++ b/Protocol/Legacy/ValidationResult.cs
@@ -0,0 +1,169 @@
+namespace Skyline.DataMiner.ProtocolValidator
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Runtime.Serialization;
+
+ using Skyline.DataMiner.CICD.Models.Protocol.Read;
+ using Skyline.DataMiner.CICD.Validators.Common.Interfaces;
+ using Skyline.DataMiner.CICD.Validators.Common.Model;
+
+ ///
+ /// Validation Result properties.
+ ///
+ ///
+ [DataContract]
+ public class ValidationResult : IValidationResult
+ {
+ public List SubResults { get; set; }
+
+ ///
+ /// Gets or sets the result.
+ /// Information, Warning or Error.
+ ///
+ [DataMember(Order = 3)]
+ public Severity Severity { get; set; }
+
+ public Source Source
+ {
+ get
+ {
+ return Source.Validator;
+ }
+ }
+
+ public List<(string Message, bool AutoFixPopup)> AutoFixWarnings { get; } = new List<(string Message, bool AutoFixPopup)>(0);
+
+ ///
+ /// Gets or sets the line number in XML file.
+ ///
+ [DataMember(Order = 4)]
+ public int Line { get; set; }
+
+ ///
+ /// Gets or sets the unique identifier for the test.
+ ///
+ public string FullId { get; set; }
+
+ ///
+ /// Gets or sets the unique identifier for the error type.
+ ///
+ [DataMember(Order = 0)]
+ public uint ErrorId { get; set; }
+
+ public uint CheckId { get; set; }
+
+ ///
+ /// Gets or sets the format for the error description.
+ ///
+ [DataMember(Order = 1)]
+ public string DescriptionFormat { get; set; }
+
+ ///
+ /// Gets or sets the name of the test.
+ ///
+ public string TestName { get; set; }
+
+ ///
+ /// Gets or sets the parameters to fill in DescriptionFormat.
+ ///
+ [DataMember(Order = 2)]
+ public object[] DescriptionParameters { get; set; }
+
+ ///
+ /// Gets or sets the position of the result in XML file.
+ ///
+ public int Position { get; set; }
+
+ public Category Category
+ {
+ get
+ {
+ return Category.Undefined;
+ }
+ }
+
+ public Certainty Certainty
+ {
+ get
+ {
+ return Certainty.Certain;
+ }
+ }
+
+ public FixImpact FixImpact
+ {
+ get
+ {
+ return FixImpact.Undefined;
+ }
+ }
+
+ public string GroupDescription
+ {
+ get
+ {
+ return String.Empty;
+ }
+ }
+
+ public string Description
+ {
+ get
+ {
+ return String.Empty;
+ }
+ }
+
+ public string HowToFix
+ {
+ get
+ {
+ return String.Empty;
+ }
+ }
+
+ public string ExampleCode
+ {
+ get
+ {
+ return String.Empty;
+ }
+ }
+
+ public string Details
+ {
+ get
+ {
+ return String.Empty;
+ }
+ }
+
+ public IReadable ReferenceNode
+ {
+ get
+ {
+ return null;
+ }
+ }
+
+ public IReadable PositionNode
+ {
+ get
+ {
+ return null;
+ }
+ }
+
+ public bool HasCodeFix
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ [DataMember(Order = 5)]
+ public (int TablePid, string Name)? DveExport { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Protocol/Legacy/ValidationResultExtensions.cs b/Protocol/Legacy/ValidationResultExtensions.cs
new file mode 100644
index 00000000..628bcc70
--- /dev/null
+++ b/Protocol/Legacy/ValidationResultExtensions.cs
@@ -0,0 +1,33 @@
+namespace Skyline.DataMiner.ProtocolValidator
+{
+ using System;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Interfaces;
+
+ internal static class ValidationResultExtensions
+ {
+ public static IValidationResult WithDveExport(this IValidationResult result, int tablePid, string name)
+ {
+ if (result == null)
+ {
+ throw new ArgumentNullException(nameof(result));
+ }
+
+ if (result is ValidationResult classResult)
+ {
+ classResult.DveExport = (tablePid, name);
+
+ if (classResult.SubResults != null)
+ {
+ foreach (var sr in classResult.SubResults)
+ {
+ // Apply recursively to sub results.
+ sr.WithDveExport(tablePid, name);
+ }
+ }
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/Protocol/Legacy/ValidatorHelperMethods.cs b/Protocol/Legacy/ValidatorHelperMethods.cs
new file mode 100644
index 00000000..603c1040
--- /dev/null
+++ b/Protocol/Legacy/ValidatorHelperMethods.cs
@@ -0,0 +1,888 @@
+namespace Skyline.DataMiner.ProtocolValidator
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Xml;
+ using System.Xml.Linq;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Interfaces;
+ using Skyline.DataMiner.CICD.Validators.Common.Model;
+
+ public partial class ProtocolChecks
+ {
+ private const string _Uri = "http://www.skyline.be/protocol";
+
+ ///
+ /// Check if any characters not allowed as element or protocol names are present in a string.
+ ///
+ /// String to test.
+ /// List of not allowed characters used in the string.
+ public List CheckBadCharacters(string s)
+ {
+ char[] notAllowed = { '<', '>', ':', '"', '/', '\\', '|', '?', '*', ';', '°' };
+ List badChars = new List();
+ foreach (char ch in notAllowed)
+ {
+ if (s.Contains(ch))
+ {
+ badChars.Add(ch);
+ }
+ }
+
+ return badChars;
+ }
+
+ ///
+ /// Collects the parameter information.
+ ///
+ /// The protocol document.
+ /// List of results.
+ public void CollectParameterInfo(XmlDocument xDoc)
+ {
+ // Clear any old data
+ ParameterInfoDictionary.Clear();
+ GroupsDictionary.Clear();
+ ParameterIdSet.Clear();
+
+ // Collect new data
+ XmlNamespaceManager xmlNsm = new XmlNamespaceManager(xDoc.NameTable);
+ xmlNsm.AddNamespace("slc", _Uri);
+
+ XmlNodeList xnlParam = xDoc.SelectNodes("/slc:Protocol/slc:Params/slc:Param", xmlNsm);
+ if (xnlParam != null)
+ {
+ foreach (XmlNode xnParam in xnlParam)
+ {
+ LineNum = xnParam.Attributes["QA_LNx"].InnerXml;
+
+ XmlNode xnId = xnParam.Attributes.GetNamedItem("id");
+ if (xnId != null)
+ {
+ string sPid = xnParam.Attributes.GetNamedItem("id").InnerXml;
+ ParameterIdSet.Add(sPid);
+
+ if (Int32.TryParse(sPid, out int iPid))
+ {
+ string sName = String.Empty;
+ string sDescription = String.Empty;
+ string sType = String.Empty;
+ string sIntType = String.Empty;
+ string sIntRawType = String.Empty;
+ string sMType = String.Empty;
+ string sPage = String.Empty;
+ string sColumn = String.Empty;
+ string sRow = String.Empty;
+ string sLineNum = String.Empty;
+ string sLengthType = String.Empty;
+ string sLengthTypeId = String.Empty;
+
+ int? duplicateAs = null;
+
+ bool bRTDisplay = false;
+ bool bTrend = false;
+ bool bAlarmed = false;
+ bool bVirtualSource = false;
+ List exports = new List();
+ bool singleExport = false;
+
+ // Get exports if present
+ string sExport = xnParam.Attributes.GetNamedItem("export")?.InnerXml;
+ if (!String.IsNullOrEmpty(sExport))
+ {
+ string[] sExports = sExport.Split(';');
+ foreach (string export in sExports)
+ {
+ if (Int32.TryParse(export, out int x))
+ {
+ if (!exports.Contains(x))
+ {
+ exports.Add(x);
+ }
+ }
+ else if (export == "true")
+ {
+ singleExport = true;
+ }
+ }
+ }
+
+ string sDuplicateAs = xnParam.Attributes.GetNamedItem("duplicateAs")?.InnerXml;
+ if (!String.IsNullOrEmpty(sExport) && Int32.TryParse(sDuplicateAs, out int d))
+ {
+ duplicateAs = d;
+ }
+
+ // Get name
+ XmlNode xnName = xnParam.SelectSingleNode("./slc:Name", xmlNsm);
+ if (xnName != null)
+ {
+ LineNum = xnName.Attributes["QA_LNx"].InnerXml;
+ sName = xnName.InnerXml;
+ }
+
+ // Get description
+ XmlNode xnDescription = xnParam.SelectSingleNode("./slc:Description", xmlNsm);
+ if (xnDescription != null)
+ {
+ LineNum = xnDescription.Attributes["QA_LNx"].InnerXml;
+ sDescription = xnDescription.InnerXml;
+ }
+
+ // Get parameter type + virtual attribute
+ XmlNode xnType = xnParam.SelectSingleNode("./slc:Type", xmlNsm);
+ if (xnType != null)
+ {
+ LineNum = xnType.Attributes["QA_LNx"].InnerXml;
+ sType = xnType.InnerXml.ToLower();
+ XmlNode xnVirtual = xnType.Attributes["virtual"];
+ if (xnVirtual != null)
+ {
+ string sv = xnVirtual.InnerText;
+ if (sv.ToLower() == "source")
+ {
+ bVirtualSource = true;
+ }
+ }
+ }
+
+ // Get parameter RTDisplay
+ XmlNode xnRTDisplay = xnParam.SelectSingleNode("./slc:Display/slc:RTDisplay", xmlNsm);
+ if (xnRTDisplay != null)
+ {
+ LineNum = xnRTDisplay.Attributes["QA_LNx"].InnerXml;
+ bRTDisplay = Boolean.TryParse(xnRTDisplay.InnerText, out bool result) && result; // In case of empty RTDisplay tag
+ }
+
+ // Get trending
+ XmlNode xnTrend = xnParam.Attributes["trending"];
+ if (xnTrend != null)
+ {
+ string trending = xnTrend.InnerXml;
+ bTrend = Boolean.Parse(trending);
+ }
+ else
+ {
+ // When trending isn't defined, it takes the value from the RTDisplay tag.
+ if (bRTDisplay)
+ {
+ bTrend = bRTDisplay;
+ }
+
+ // Capture write and write bit => Default false when write parameter.
+ if (sType.ToLower().Contains("write"))
+ {
+ bTrend = false;
+ }
+ }
+
+ // Get interprete type
+ XmlNode xnIntType = xnParam.SelectSingleNode("./slc:Interprete/slc:Type", xmlNsm);
+ if (xnIntType != null)
+ {
+ LineNum = xnIntType.Attributes["QA_LNx"].InnerXml;
+ sIntType = xnIntType.InnerXml.ToLower();
+ }
+
+ // Get interprete raw type
+ XmlNode xnIntRawType = xnParam.SelectSingleNode("./slc:Interprete/slc:RawType", xmlNsm);
+ if (xnIntRawType != null)
+ {
+ LineNum = xnIntRawType.Attributes["QA_LNx"].InnerXml;
+ sIntRawType = xnIntRawType.InnerXml.ToLower();
+ }
+
+ // Get parameter measurement type
+ XmlNode xnMType = xnParam.SelectSingleNode("./slc:Measurement/slc:Type", xmlNsm);
+ if (xnMType != null)
+ {
+ LineNum = xnMType.Attributes["QA_LNx"].InnerXml;
+ sMType = xnMType.InnerXml.ToLower();
+ }
+
+ // Get parameter lengthType
+ XmlNode xnLengthType = xnParam.SelectSingleNode("./slc:Interprete/slc:LengthType", xmlNsm);
+ if (xnLengthType != null)
+ {
+ LineNum = xnLengthType.Attributes["QA_LNx"].InnerXml;
+ sLengthType = xnLengthType.InnerXml.ToLower();
+
+ // Get parameter lengthType ID
+ XmlNode xnLengthTypeId = xnLengthType.SelectSingleNode("./@id", xmlNsm);
+ if (xnLengthTypeId != null)
+ {
+ sLengthTypeId = xnLengthTypeId.InnerXml;
+ }
+ }
+
+ // Get parameter LineNumber
+ XmlNode xnLineNum = xnParam.Attributes["QA_LNx"];
+ if (xnLineNum != null)
+ {
+ sLineNum = xnLineNum.InnerXml;
+ }
+
+ // Get alarm monitored
+ XmlNode xnAlarm = xnParam.SelectSingleNode("./slc:Alarm/slc:Monitored", xmlNsm);
+ if (xnAlarm != null)
+ {
+ string monitored = xnAlarm.InnerXml;
+ bAlarmed = Convert.ToBoolean(monitored);
+ }
+
+ // Get parameter positions
+ XmlNodeList xnlPosition = xnParam.SelectNodes("./slc:Display/slc:Positions/slc:Position", xmlNsm);
+ if (xnlPosition.Count != 0)
+ {
+ List poslist = new List();
+ foreach (XmlNode xnPosition in xnlPosition)
+ {
+ LineNum = xnPosition.Attributes["QA_LNx"].InnerXml;
+ XmlNode xnPage = xnPosition.SelectSingleNode("./slc:Page", xmlNsm);
+ if (xnPage != null)
+ {
+ LineNum = xnPage.Attributes["QA_LNx"].InnerXml;
+ sPage = xnPage.InnerXml;
+ }
+
+ XmlNode xnRow = xnPosition.SelectSingleNode("./slc:Row", xmlNsm);
+ if (xnRow != null)
+ {
+ LineNum = xnRow.Attributes["QA_LNx"].InnerXml;
+ sRow = xnRow.InnerXml;
+ }
+
+ XmlNode xnColumn = xnPosition.SelectSingleNode("./slc:Column", xmlNsm);
+ if (xnColumn != null)
+ {
+ LineNum = xnColumn.Attributes["QA_LNx"].InnerXml;
+ sColumn = xnColumn.InnerXml;
+ }
+
+ if (!String.IsNullOrEmpty(sPage) && !String.IsNullOrEmpty(sRow) && !String.IsNullOrEmpty(sColumn)) // Position data is OK
+ {
+ if (exports.Count == 0 && !singleExport)
+ {
+ poslist.Add(new Position(sPage, sRow, sColumn));
+ }
+ else
+ {
+ if (singleExport)
+ {
+ Position position = new Position
+ {
+ Page = sPage,
+ Row = sRow,
+ Column = sColumn,
+ Export = -1
+ };
+ poslist.Add(position);
+ }
+ else
+ {
+ foreach (int export in exports)
+ {
+ // Need to use new position, else the same object will be added to list twice.
+ Position position = new Position
+ {
+ Page = sPage,
+ Row = sRow,
+ Column = sColumn,
+ Export = export
+ };
+
+ poslist.Add(position);
+ }
+ }
+ }
+ }
+ else // No position data, add positions for exports
+ {
+ if (singleExport)
+ {
+ Position position = new Position
+ {
+ Export = -1
+ };
+ poslist.Add(position);
+ }
+
+ if (exports.Count > 0)
+ {
+ foreach (int export in exports)
+ {
+ // Need to use new position, else the same object will be added to list multiple times.
+ Position newposition = new Position
+ {
+ Export = export
+ };
+ poslist.Add(newposition);
+ }
+ }
+ }
+ }
+
+ Position emptyPosition = new Position();
+ while (poslist.Count > 1 && poslist.Contains(emptyPosition))
+ {
+ poslist.Remove(emptyPosition);
+ }
+
+ ParameterInfo pi2 = new ParameterInfo(iPid, sName, sDescription, sType, sIntType, sIntRawType, sMType,
+ bRTDisplay, sLineNum, sLengthType, sLengthTypeId, bTrend, bAlarmed, bVirtualSource, xnParam,
+ poslist.ToArray());
+
+ pi2.DuplicateAs = duplicateAs;
+
+ if (ParameterInfoDictionary.ContainsKey(Convert.ToInt32(sPid)))
+ {
+ continue;
+ }
+
+ ParameterInfoDictionary.Add(Convert.ToInt32(sPid), pi2);
+ }
+ else
+ {
+ List poslist = new List();
+
+ if (singleExport)
+ {
+ Position position = new Position
+ {
+ Export = -1
+ };
+ poslist.Add(position);
+ }
+
+ if (exports.Count > 0)
+ {
+ foreach (int export in exports)
+ {
+ Position newposition = new Position
+ {
+ Export = export
+ };
+ poslist.Add(newposition);
+ }
+ }
+
+ ParameterInfo pi = new ParameterInfo(iPid, sName, sDescription, sType, sIntType, sIntRawType, sMType,
+ bRTDisplay, sLineNum, sLengthType, sLengthTypeId, bTrend, bAlarmed, bVirtualSource, xnParam,
+ poslist.ToArray());
+
+ pi.DuplicateAs = duplicateAs;
+
+ if (ParameterInfoDictionary.ContainsKey(Convert.ToInt32(sPid)))
+ {
+ continue;
+ }
+
+ ParameterInfoDictionary.Add(Convert.ToInt32(sPid), pi);
+ }
+ }
+ }
+ }
+ }
+
+ XmlNodeList xnlGroup = xDoc.SelectNodes("/slc:Protocol/slc:Groups/slc:Group", xmlNsm);
+ foreach (XmlNode xnGroup in xnlGroup)
+ {
+ if (xnGroup != null)
+ {
+ LineNum = xnGroup.Attributes["QA_LNx"].InnerXml;
+ string groupId = xnGroup.Attributes.GetNamedItem("id").InnerXml;
+
+ if (GroupsDictionary.ContainsKey(Convert.ToInt32(groupId)))
+ {
+ continue;
+ }
+
+ GroupsDictionary.Add(Convert.ToInt32(groupId), xnGroup);
+ }
+ }
+ }
+
+ ///
+ /// Gets all tables.
+ ///
+ /// The protocol document.
+ /// List of tables.
+ public List GetAllTables(XmlDocument xDoc)
+ {
+ XmlNamespaceManager xmlNsm = new XmlNamespaceManager(xDoc.NameTable);
+ xmlNsm.AddNamespace("slc", _Uri);
+
+ XmlNodeList xnlTables = xDoc.SelectNodes("slc:Protocol/slc:Params/slc:Param[slc:Type='array']", xmlNsm);
+ XmlNodeList xnlTables2 = xDoc.SelectNodes("slc:Protocol/slc:Params/slc:Param[slc:Type='ARRAY']", xmlNsm);
+ var allTables = xnlTables.Cast().Concat(xnlTables2.Cast()).ToList();
+ return allTables;
+ }
+
+ ///
+ /// Gets the duplicated parameters.
+ ///
+ /// The protocol document.
+ /// List of results.
+ public List GetDuplicatedParams(XmlDocument xDoc) // M
+ {
+ List resultMsg = new List();
+
+ DuplicateParameterDictionary.Clear();
+
+ XmlNodeList xnlParam = xDoc.GetElementsByTagName("Param");
+ foreach (XmlNode xnParam in xnlParam)
+ {
+ LineNum = xnParam.Attributes?["QA_LNx"]?.InnerXml;
+ string sPid = xnParam.Attributes?.GetNamedItem("id")?.InnerText;
+
+ if (Int32.TryParse(sPid, out int iPid))
+ {
+ string sDuplicateAs = xnParam.Attributes?.GetNamedItem("duplicateAs")?.InnerText;
+ if (!String.IsNullOrEmpty(sDuplicateAs))
+ {
+ string[] asDuplicateAsPids = sDuplicateAs.Split(',');
+ foreach (string sDuplicateAsPid in asDuplicateAsPids)
+ {
+ if (Int32.TryParse(sDuplicateAsPid, out int iDuplicateAsPid))
+ {
+ DuplicateParameterDictionary.Add(iDuplicateAsPid, iPid);
+ }
+ else
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 1001,
+ DescriptionFormat = "Internal Application Error : Error in {0}. Could not parse sDuplicateAsPid '{1}' to an integer.",
+ DescriptionParameters = new object[] { "GetDuplicatedParams", sDuplicateAsPid },
+ TestName = "GetDuplicatedParams",
+ Severity = Severity.Critical
+ });
+ }
+ }
+ }
+ }
+ }
+
+ return resultMsg;
+ }
+
+ ///
+ /// Gets the real pid.
+ ///
+ /// The duplicated pid.
+ /// The real pid.
+ public int GetRealPid(int iDuplicatedPid)
+ {
+ if (!DuplicateParameterDictionary.TryGetValue(iDuplicatedPid, out int iRealPid))
+ {
+ iRealPid = iDuplicatedPid;
+ }
+
+ return iRealPid;
+ }
+
+ ///
+ /// Determines whether the specified string is an .
+ ///
+ /// The string.
+ ///
+ /// true if the specified string is an integer; otherwise, false.
+ ///
+ public bool IsInteger(string s)
+ {
+ return Int32.TryParse(s, out int i);
+ }
+
+ ///
+ /// Loads the line numbers.
+ ///
+ /// The protocol document.
+ public XmlDocument LoadWithLineNums(string xml) // S
+ {
+ // Add line numbers
+ XDocument xInput = XDocument.Parse(xml, LoadOptions.SetLineInfo);
+ XElement root = xInput.Root;
+ XNamespace skylineProtocol = "http://www.skyline.be/protocol";
+
+ // Add root namespace if needed
+ if (root.GetDefaultNamespace() != skylineProtocol)
+ {
+ root.Name = skylineProtocol.GetName(root.Name.LocalName);
+ foreach (XElement el in root.Descendants())
+ {
+ if (el.GetDefaultNamespace() != skylineProtocol)
+ {
+ el.Name = skylineProtocol.GetName(el.Name.LocalName);
+ }
+
+ if (el.NodeType == XmlNodeType.Element)
+ {
+ // Add lineInfo
+ IXmlLineInfo ili = el;
+ if (ili.HasLineInfo())
+ {
+ int line = ili.LineNumber;
+ XAttribute xline = new XAttribute("QA_LNx", line);
+ el.Add(xline);
+ }
+ }
+ }
+ }
+ else
+ {
+ foreach (XElement xni in xInput.Descendants())
+ {
+ if (xni.NodeType == XmlNodeType.Element)
+ {
+ // Add lineInfo
+ IXmlLineInfo ili = xni;
+ if (ili.HasLineInfo())
+ {
+ int line = ili.LineNumber;
+ XAttribute xline = new XAttribute("QA_LNx", line);
+ xni.Add(xline);
+ }
+ }
+ }
+ }
+
+ XmlDocument xDoc = new XmlDocument();
+ xDoc.LoadXml(xInput.ToString());
+
+ return xDoc;
+ }
+
+ ///
+ /// Returns a Dictionary with (PID, index) pairs. For columns in the type tag, indexes are auto-incremented, skipping indexes defined in ColumnOptions.
+ ///
+ /// The protocol document.
+ /// Table Parameter ID.
+ /// Optional resultMsg for errors, should only be used when calling from 17xx tests.
+ /// Dictionary with (PID, index) pairs.
+ private Dictionary GetColumnPids(XmlDocument xDoc, string tablePid, List resultMsg = default(List))
+ {
+ Dictionary columnPids = new Dictionary();
+
+ // Add xmlNameSpaceManager
+ XmlNamespaceManager xmlNsm = new XmlNamespaceManager(xDoc.NameTable);
+ xmlNsm.AddNamespace("slc", _Uri);
+
+ XmlNode xnParam = ParameterInfoDictionary[Convert.ToInt32(tablePid)].Element;
+
+ if (xnParam == null) { return columnPids; }
+
+ LineNum = xnParam.Attributes?["QA_LNx"].InnerXml;
+
+ // Add id's in ColumnOption tags to pidsToCheck List
+ XmlNodeList xnlColumnOptions = xnParam.SelectNodes(".//slc:ColumnOption", xmlNsm);
+ foreach (XmlNode xnColumnOption in xnlColumnOptions)
+ {
+ LineNum = xnColumnOption.Attributes?["QA_LNx"].InnerXml;
+ string columnOptionPid = xnColumnOption.Attributes?["pid"]?.InnerXml;
+ if (columnOptionPid == null) { continue; }
+
+ string columnOptionIdx = xnColumnOption.Attributes?["idx"]?.InnerXml;
+ if (columnOptionIdx == null) { continue; }
+
+ if (!columnPids.Keys.Contains(columnOptionPid))
+ {
+ columnPids.Add(columnOptionPid, columnOptionIdx);
+ }
+ else if (resultMsg != default(List)) // Generate error only once
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 1703,
+ DescriptionFormat = "Table Column Parameter {0} is added to table {1} more than once.",
+ DescriptionParameters = new object[] { columnOptionPid, tablePid },
+ TestName = "CheckTableColumnParams",
+ Severity = Severity.Critical
+ });
+ }
+ }
+
+ // Add id's in type tag to columnPids List
+ XmlNode xnType = xnParam.SelectSingleNode("slc:Type", xmlNsm);
+ if (xnType != null && String.Equals(xnType.InnerXml, "array", StringComparison.OrdinalIgnoreCase) && xnType.Attributes?["id"] != null)
+ {
+ LineNum = xnType.Attributes?["QA_LNx"].InnerXml;
+ string sId = xnType.Attributes["id"].InnerXml;
+ string[] ids = sId.Split(';');
+ int counter = 0;
+ foreach (string id in ids)
+ {
+ if (String.IsNullOrEmpty(id)) { continue; }
+
+ while (columnPids.Values.Contains(counter.ToString()))
+ {
+ counter++;
+ }
+
+ if (!columnPids.Keys.Contains(id))
+ {
+ columnPids.Add(id, counter.ToString());
+ }
+ else if (resultMsg != default(List))
+ {
+ resultMsg.Add(new ValidationResult
+ {
+ Line = Convert.ToInt32(LineNum),
+ ErrorId = 1703,
+ DescriptionFormat = "Table Column Parameter {0} is added to table {1} more than once.",
+ DescriptionParameters = new object[] { id, tablePid },
+ TestName = "CheckTableColumnParams",
+ Severity = Severity.Critical
+ });
+ }
+
+ counter++;
+ }
+ }
+
+ return columnPids;
+ }
+
+ ///
+ /// Get a list of exported table pids for main tables.
+ ///
+ /// Skyline DataMiner protocol XmlDocument.
+ /// List of exported tables.
+ private Dictionary GetDveTables(XmlDocument xDoc) // M
+ {
+ XmlNamespaceManager xmlNsm = new XmlNamespaceManager(xDoc.NameTable);
+ xmlNsm.AddNamespace("slc", _Uri);
+
+ Dictionary exportedTables = new Dictionary();
+
+ XmlNodeList xnlType = xDoc.GetElementsByTagName("Type");
+ XmlNode xnType = xnlType.Item(0);
+ LineNum = xnType?.Attributes?["QA_LNx"]?.InnerXml;
+ string typeOptions = xnType?.Attributes?.GetNamedItem("options")?.InnerXml;
+
+ if (!String.IsNullOrEmpty(typeOptions) && typeOptions.Contains("exportProtocol"))
+ {
+ string[] options = typeOptions.Split(';');
+
+ foreach (string option in options)
+ {
+ string[] optionDetails = option.Split(':');
+ if (optionDetails.Length >= 3 && optionDetails[0] == "exportProtocol")
+ {
+ exportedTables.Add(optionDetails[2], optionDetails[1]);
+ }
+ }
+ }
+
+ XmlNodeList xnlDveProtocol = xDoc.SelectNodes("slc:Protocol/slc:DVEs/slc:DVEProtocols/slc:DVEProtocol", xmlNsm);
+ foreach (XmlNode dveProtocol in xnlDveProtocol)
+ {
+ LineNum = dveProtocol.Attributes?["QA_LNx"]?.InnerXml;
+ string name = dveProtocol.Attributes?["name"]?.InnerXml;
+ string tablePid = dveProtocol.Attributes?["tablePID"]?.InnerXml;
+ if (!String.IsNullOrEmpty(name) && !String.IsNullOrEmpty(tablePid))
+ {
+ exportedTables.Add(tablePid, name);
+ }
+ }
+
+ return exportedTables;
+ }
+
+ ///
+ /// Gets the inner XML or CData.
+ ///
+ /// The node.
+ /// Inner XML as string.
+ private string GetInnerXmlOrCData(XmlNode node)
+ {
+ // Get string or CDATA content
+ string content = String.Empty;
+ if (!node.HasChildNodes) // No child nodes, just return innerXML
+ {
+ return node.InnerXml;
+ }
+
+ foreach (XmlNode child in node.ChildNodes)
+ {
+ if (child.NodeType == XmlNodeType.Comment)
+ {
+ continue; // We're not interested in comments
+ }
+
+ if (child.NodeType == XmlNodeType.CDATA)
+ {
+ content = child.InnerText;
+ break; // First CDATA node will be used (there should only be one)
+ }
+ else // No CDATA content, just read innerXml
+ {
+ content = node.InnerXml;
+ break;
+ }
+ }
+
+ return content;
+ }
+
+ ///
+ /// Gets the read pid for a write pid.
+ ///
+ /// Write parameter id.
+ /// Corresponding read parameter id, -1 if no read parameter id is found.
+ private int GetReadPid(int writePid)
+ {
+ int readPid = -1; // Default if no write exists.
+ string writeDescription = ParameterInfoDictionary[writePid].Description;
+ ParameterInfo piRead = ParameterInfoDictionary.Values.FirstOrDefault(pi => pi.Description == writeDescription && pi.Type == "read");
+ if (piRead != null)
+ {
+ readPid = Convert.ToInt32(piRead.Pid);
+ }
+
+ return readPid;
+ }
+
+ ///
+ /// Gets the table foreign keys.
+ ///
+ /// The table.
+ /// The namespace.
+ /// List of table ids.
+ private List GetTableForeignKeys(XmlNode table, XmlNamespaceManager xmlNsm)
+ {
+ List keys = new List();
+
+ // Return empty list immediately if node is not an element
+ if (table.NodeType != XmlNodeType.Element)
+ {
+ return keys;
+ }
+
+ // Find foreignKeys
+ // Find tables with foreignKey to exported table that are not in a relation, in this case the table will be exported.
+ XmlNodeList xnlCOoptions = table.SelectNodes("./slc:ArrayOptions/slc:ColumnOption/@options", xmlNsm);
+ if (xnlCOoptions != null)
+ {
+ foreach (XmlNode options in xnlCOoptions)
+ {
+ string sOptions = options.InnerXml;
+ if (!String.IsNullOrEmpty(sOptions))
+ {
+ string[] asAllOptions = sOptions.Split(new[] { sOptions[0] }, StringSplitOptions.RemoveEmptyEntries);
+ foreach (string option in asAllOptions)
+ {
+ if (option.StartsWith("foreignkey=", StringComparison.InvariantCultureIgnoreCase))
+ {
+ string fkTable = option.Substring("foreignkey=".Length);
+ if (Int32.TryParse(fkTable, out int iTable))
+ {
+ keys.Add(iTable);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return keys;
+ }
+
+ ///
+ /// Gets the table measurement pids.
+ ///
+ /// The table parameter.
+ /// If set to true [Duplicated as to main].
+ /// HashSet of Pids.
+ private HashSet GetTableMeasurementPids(XmlNode tableParam, bool bDuplicatedAsToMain = true)
+ {
+ XmlNamespaceManager xmlNsm = new XmlNamespaceManager(tableParam.OwnerDocument.NameTable);
+ xmlNsm.AddNamespace("slc", _Uri);
+ HashSet result = new HashSet();
+
+ // Get all column parameters in measurement
+ XmlAttribute measurementTypeOptions = (XmlAttribute)tableParam.SelectSingleNode("./slc:Measurement/slc:Type/@options", xmlNsm);
+ if (measurementTypeOptions == null)
+ {
+ return result;
+ }
+
+ LineNum = measurementTypeOptions.OwnerElement?.Attributes["QA_LNx"].InnerXml;
+ string options = measurementTypeOptions.InnerXml;
+ options = options.TrimStart(';');
+ string[] asOptions = options.Split(';');
+ foreach (string s in asOptions)
+ {
+ const string Tab = "tab=";
+ if (s.StartsWith(Tab, StringComparison.InvariantCulture))
+ {
+ string taboption = s.Substring(s.IndexOf(Tab, StringComparison.InvariantCulture) + Tab.Length);
+ string[] asTabOptions = taboption.Split(',');
+ foreach (string stab in asTabOptions)
+ {
+ const string Col = "columns:";
+ if (stab.StartsWith(Col, StringComparison.InvariantCulture))
+ {
+ string columns = stab.Substring(stab.IndexOf(Col, StringComparison.InvariantCulture) + Col.Length);
+ string[] asColumns = columns.Split('-');
+ foreach (string column in asColumns)
+ {
+ string sPid;
+ if (column.Contains('|'))
+ {
+ // Format PID|IDX is used
+ sPid = column.Substring(0, column.IndexOf('|'));
+ }
+ else
+ {
+ // Content is parameter id
+ sPid = column;
+ }
+
+ if (Int32.TryParse(sPid, out int iPid))
+ {
+ int iRealPid;
+ if (bDuplicatedAsToMain)
+ {
+ // If parameter id a duplicated parameter, run check on original parameter
+ iRealPid = GetRealPid(iPid);
+ }
+ else
+ {
+ iRealPid = iPid;
+ }
+
+ result.Add(iRealPid);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Gets the write pid for a read pid.
+ ///
+ /// Read parameter id.
+ /// Corresponding write parameter id, -1 if no write parameter id is found.
+ private int GetWritePid(int readPid)
+ {
+ int writePid = -1; // Default if no write exists.
+ if (ParameterInfoDictionary.ContainsKey(readPid))
+ {
+ string readDescription = ParameterInfoDictionary[readPid].Description;
+ ParameterInfo piWrite = ParameterInfoDictionary.Values.FirstOrDefault(pi => pi.Description == readDescription && pi.Type == "write");
+ if (piWrite != null)
+ {
+ writePid = Convert.ToInt32(piWrite.Pid);
+ }
+ }
+
+ return writePid;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Protocol/Protocol.csproj b/Protocol/Protocol.csproj
index 7af7356f..265f636c 100644
--- a/Protocol/Protocol.csproj
+++ b/Protocol/Protocol.csproj
@@ -1,6 +1,6 @@
- netstandard2.0
+ netstandard2.0
disable
disable
Skyline.DataMiner.CICD.Validators.Protocol
@@ -37,5 +37,13 @@
+
+
+
+
+
+
+
+
diff --git a/Protocol/Tests/Protocol/QActions/QAction/CSharpCheckUnrecommendedPropertySet.cs b/Protocol/Tests/Protocol/QActions/QAction/CSharpCheckUnrecommendedPropertySet.cs
index 2c2a0eca..92e279c3 100644
--- a/Protocol/Tests/Protocol/QActions/QAction/CSharpCheckUnrecommendedPropertySet.cs
+++ b/Protocol/Tests/Protocol/QActions/QAction/CSharpCheckUnrecommendedPropertySet.cs
@@ -63,8 +63,10 @@ public List Validate(ValidatorContext context)
internal class QActionAnalyzer : CSharpAnalyzerBase
{
- private static readonly System.Type ThreadType = typeof(Thread);
- private static readonly string ThreadAssemblyName = ThreadType.Assembly.GetName().Name;
+ private static readonly System.Type ThreadType = typeof(Thread);
+
+ // QActions are in .NET Framework
+ private const string ThreadAssemblyName = "mscorlib";
private readonly List results;
private readonly IValidate test;
diff --git a/ProtocolTests.SchemaGenerator/Program.cs b/ProtocolTests.SchemaGenerator/Program.cs
new file mode 100644
index 00000000..084ea885
--- /dev/null
+++ b/ProtocolTests.SchemaGenerator/Program.cs
@@ -0,0 +1,43 @@
+namespace SLDisValidatorUnitTestsSchemaGenerator
+{
+ using System;
+ using System.IO;
+ using System.Reflection;
+
+ internal class Program
+ {
+ private static void Main(string[] args)
+ {
+ string solutionDir = String.Join(" ", args);
+
+ var assembly = typeof(SchemaGenerator).Assembly;
+ var buildConfigurationName = assembly.GetCustomAttribute()?.Configuration;
+
+ var projectName = assembly.GetName().Name;
+ string schema;
+
+ try
+ {
+ string path = Path.Combine(solutionDir, projectName, "bin", buildConfigurationName, "net472", "Skyline", "XSD", "protocol.xsd");
+ SchemaGenerator generator = new SchemaGenerator(path);
+ schema = generator.CreateSchema();
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ Environment.Exit(-1);
+ return;
+ }
+
+ try
+ {
+ File.WriteAllText(Path.Combine(solutionDir, "ProtocolTests", "ValidatorUnitTestProtocols.g.xsd"), schema);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ Environment.Exit(-2);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ProtocolTests.SchemaGenerator/ProtocolTests.SchemaGenerator.csproj b/ProtocolTests.SchemaGenerator/ProtocolTests.SchemaGenerator.csproj
new file mode 100644
index 00000000..3a7911ec
--- /dev/null
+++ b/ProtocolTests.SchemaGenerator/ProtocolTests.SchemaGenerator.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ net472
+ $(MSBuildProjectName)
+ $(MSBuildProjectName)
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ProtocolTests.SchemaGenerator/SchemaGenerator.cs b/ProtocolTests.SchemaGenerator/SchemaGenerator.cs
new file mode 100644
index 00000000..34d51dc2
--- /dev/null
+++ b/ProtocolTests.SchemaGenerator/SchemaGenerator.cs
@@ -0,0 +1,238 @@
+namespace SLDisValidatorUnitTestsSchemaGenerator
+{
+ using System;
+ using System.Reflection;
+ using System.Xml;
+
+ public class SchemaGenerator
+ {
+ private const string TargetNamespace = "http://www.skyline.be/validatorProtocolUnitTest";
+
+ private readonly string inputFile = "";
+
+ private XmlDocument doc;
+ private XmlNamespaceManager nsmgr;
+
+ public SchemaGenerator(string inputFile)
+ {
+ if (string.IsNullOrEmpty(inputFile))
+ {
+ throw new ArgumentException("Input file parameter must not be null or empty.", "inputFile");
+ }
+
+ this.inputFile = inputFile;
+ }
+
+ ///
+ /// Creates XML Schema to be used for creating unit tests for the protocol validator.
+ ///
+ /// The complete XSD.
+ /// The loosened XSD.
+ public string CreateSchema()
+ {
+ try
+ {
+ doc = new XmlDocument();
+ doc.Load(inputFile);
+
+ nsmgr = new XmlNamespaceManager(doc.NameTable);
+ nsmgr.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema");
+
+ UpdateNamespaces();
+
+ UpdateSchemaLocation();
+
+ MakeElementsOptional();
+ MakeAttributesOptional();
+
+ RemoveKeysAndKeyReferences();
+
+ return doc.OuterXml;
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e.ToString());
+ return "";
+ }
+ }
+
+ ///
+ /// Updates the namespaces.
+ ///
+ private void UpdateNamespaces()
+ {
+ XmlNodeList targetNamespaceAttr = doc.DocumentElement.SelectNodes("./@targetNamespace");
+ if (targetNamespaceAttr.Count > 0)
+ {
+ foreach (XmlNode attr in targetNamespaceAttr)
+ {
+ attr.Value = TargetNamespace;
+ }
+ }
+
+ XmlAttribute disnsAttr = doc.CreateAttribute("xmlns:dis");
+ disnsAttr.Value = TargetNamespace;
+
+ XmlAttribute xmlnsAttr = doc.CreateAttribute("xmlns");
+ xmlnsAttr.Value = TargetNamespace;
+
+ doc.DocumentElement.Attributes.Append(xmlnsAttr);
+ doc.DocumentElement.Attributes.Append(disnsAttr);
+ }
+
+ private void UpdateSchemaLocation()
+ {
+ XmlNodeList elementList = doc.DocumentElement.SelectNodes(".//xs:include", nsmgr);
+
+ var assembly = typeof(SchemaGenerator).Assembly;
+ var buildConfigurationName = assembly.GetCustomAttribute()?.Configuration;
+
+ var projectName = assembly.GetName().Name;
+ foreach (XmlNode node in elementList)
+ {
+ var result = node.Attributes["schemaLocation"];
+ if (result != null)
+ {
+ result.Value = $@".\..\{projectName}\bin\{buildConfigurationName}\net472\Skyline\XSD\" + result.Value;
+ }
+ }
+ }
+
+ ///
+ /// Makes the elements optional.
+ ///
+ private void MakeElementsOptional()
+ {
+ XmlNodeList elementList = doc.DocumentElement.SelectNodes(".//xs:element", nsmgr);
+
+ foreach (XmlNode node in elementList)
+ {
+ XmlNodeList minOccursAttrs = node.SelectNodes("./@minOccurs");
+ if (minOccursAttrs.Count > 0)
+ {
+ bool isHttpRequestNode = IsHttpRequestNode(node);
+ bool isExposerNode = IsExposerNode(node);
+
+ if (!isHttpRequestNode && !isExposerNode)
+ {
+ foreach (XmlNode minOccursAttr in minOccursAttrs)
+ {
+ if (!minOccursAttr.Value.Equals("0"))
+ {
+ minOccursAttr.Value = "0";
+ }
+ }
+ }
+ }
+ else
+ {
+ bool isRootNode = node.Attributes["name"].Value == "Protocol" && node.ParentNode.Name == "xs:schema";
+
+ if (!isRootNode)
+ {
+ XmlAttribute minOccursAttr = doc.CreateAttribute("minOccurs");
+ minOccursAttr.Value = "0";
+
+ node.Attributes.Append(minOccursAttr);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Determines whether the specified node is an Exposer node, as these cannot be made optional as otherwise the content model would become ambiguous rendering the XML Schema invalid.
+ ///
+ ///
+ private bool IsExposerNode(XmlNode node)
+ {
+ bool result = false;
+
+ string name = node.Attributes["name"].Value;
+
+ if (name.Equals("Exposer"))
+ {
+ result = true;
+ }
+
+ return result;
+ }
+
+ ///
+ /// Determines whether the specified node is an HTTP request node, as these cannot be made optional as otherwise the content model would become ambiguous rendering the XML Schema invalid.
+ ///
+ ///
+ private bool IsHttpRequestNode(XmlNode node)
+ {
+ bool result = false;
+
+ string name = node.Attributes["name"].Value;
+
+ var typeAttr = node.Attributes["type"];
+
+ if (typeAttr != null)
+ {
+ string type = node.Attributes["type"].Value;
+
+ if ((name.Equals("Headers") || name == "Parameters" || name == "Data") && (type == "HttpRequestHeaders" || type == "HttpRequestData" || type == "HttpRequestParameters"))
+ {
+ result = true;
+ }
+ }
+
+ return result;
+ }
+
+
+ ///
+ /// Makes the attributes optional.
+ ///
+ private void MakeAttributesOptional()
+ {
+ XmlNodeList attributeList = doc.DocumentElement.SelectNodes(".//xs:attribute", nsmgr);
+
+ foreach (XmlNode node in attributeList)
+ {
+ XmlNodeList minOccursAttrs = node.SelectNodes("./@use");
+ if (minOccursAttrs.Count > 0)
+ {
+ foreach (XmlNode minOccursAttr in minOccursAttrs)
+ {
+ if (!minOccursAttr.Value.Equals("optional"))
+ {
+ minOccursAttr.Value = "optional";
+ }
+ }
+ }
+ else
+ {
+ XmlAttribute minOccursAttr = doc.CreateAttribute("use");
+ minOccursAttr.Value = "optional";
+
+ node.Attributes.Append(minOccursAttr);
+ }
+ }
+ }
+
+ ///
+ /// Removes the key reference constraints.
+ ///
+ private void RemoveKeysAndKeyReferences()
+ {
+ XmlNodeList keyRefList = doc.DocumentElement.SelectNodes(".//xs:keyref", nsmgr);
+
+ foreach (XmlNode node in keyRefList)
+ {
+ XmlNode parent = node.ParentNode;
+ parent.RemoveChild(node);
+ }
+
+ XmlNodeList keyList = doc.DocumentElement.SelectNodes(".//xs:key", nsmgr);
+
+ foreach (XmlNode node in keyList)
+ {
+ XmlNode parent = node.ParentNode;
+ parent.RemoveChild(node);
+ }
+ }
+ }
+}
diff --git a/ProtocolTests/CheckUnitTests.cs b/ProtocolTests/CheckUnitTests.cs
new file mode 100644
index 00000000..22b4d21c
--- /dev/null
+++ b/ProtocolTests/CheckUnitTests.cs
@@ -0,0 +1,1729 @@
+namespace SLDisValidatorUnitTests
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.Immutable;
+ using System.IO;
+ using System.Linq;
+ using System.Reflection;
+ using System.Text;
+ using System.Text.RegularExpressions;
+ using System.Xml;
+ using System.Xml.Schema;
+
+ using FluentAssertions;
+
+ using Microsoft.CodeAnalysis;
+ using Microsoft.CodeAnalysis.CSharp;
+ using Microsoft.CodeAnalysis.CSharp.Syntax;
+ using Microsoft.CodeAnalysis.FindSymbols;
+ using Microsoft.CodeAnalysis.MSBuild;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.CSharpAnalysis;
+ using Skyline.DataMiner.CICD.CSharpAnalysis.Classes;
+ using Skyline.DataMiner.CICD.CSharpAnalysis.Enums;
+ using Skyline.DataMiner.CICD.Validators.Common.Interfaces;
+ using Skyline.DataMiner.CICD.Validators.Common.Model;
+
+ using SLDisValidator2;
+ using SLDisValidator2.Common;
+ using SLDisValidator2.Common.Attributes;
+ using SLDisValidator2.Interfaces;
+
+ [TestClass]
+ [Ignore("TODO: Fix")]
+ public class CheckTests
+ {
+ ///
+ /// Checks if there is a namespace for each test.
+ ///
+ [TestMethod]
+ public void CheckTestsForUnitTest_Validate()
+ {
+ // Get all tests
+ var allTests = TestCollector.GetAllValidateTests().Tests;
+
+ // Get all namespaces
+ var namespaces = Assembly
+ .GetAssembly(typeof(CheckTests))
+ .GetTypes()
+ .Select(type => type.Namespace)
+ .ToList();
+
+ List testNamespaces = new List();
+ foreach ((IValidate test, TestAttribute _) in allTests)
+ {
+ // Get test name
+ Type temp = test.GetType();
+
+ string newNamespace = temp.Namespace?.Replace("SLDisValidator2.Tests", "SLDisValidatorUnitTests");
+
+ if (!namespaces.Contains(newNamespace))
+ {
+ testNamespaces.Add(newNamespace?.Remove(0, "SLDisValidatorUnitTests.".Length));
+ }
+ }
+
+ // Check if UnitTest already exists
+ testNamespaces.Should().BeEmpty();
+ }
+
+ ///
+ /// Checks if there is a namespace for each test.
+ ///
+ [TestMethod]
+ public void CheckTestsForUnitTest_Compare()
+ {
+ // Get all tests
+ var allTests = TestCollector.GetAllCompareTests().Tests;
+
+ // Get all namespaces
+ var namespaces = Assembly
+ .GetAssembly(typeof(CheckTests))
+ .GetTypes()
+ .Select(type => type.Namespace)
+ .ToList();
+
+ List testNamespaces = new List();
+ foreach ((ICompare test, TestAttribute _) in allTests)
+ {
+ // Get test name
+ Type temp = test.GetType();
+
+ string newNamespace = temp.Namespace?.Replace("SLDisValidator2.Tests", "SLDisValidatorUnitTests");
+
+ if (!namespaces.Contains(newNamespace))
+ {
+ testNamespaces.Add(newNamespace?.Remove(0, "SLDisValidatorUnitTests.".Length));
+ }
+ }
+
+ // Check if UnitTest already exists
+ testNamespaces.Should().BeEmpty();
+ }
+
+ [TestMethod]
+ public void CheckNamespacesOfUnitTests()
+ {
+ // Get all namespaces
+ var namespaces = Assembly
+ .GetAssembly(typeof(CheckTests))
+ .GetTypes()
+ .Select(x => x.Namespace.Replace("SLDisValidatorUnitTests.", String.Empty));
+
+ // Get all Files
+ var currentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
+
+ var files = Directory
+ .GetParent(currentDirectory)
+ .Parent
+ .Parent
+ .GetFiles("*.cs", SearchOption.AllDirectories);
+
+ files.Should().NotBeEmpty();
+
+ List lsWrongFiles = new List();
+
+ string[] asExcludeDirectories =
+ {
+ "bin", "obj", "Generic Tests", "Software Parameters",
+ };
+
+ foreach (var item in files)
+ {
+ if (String.Equals(item.Directory.Name, "ProtocolTests"))
+ {
+ // No need to check generic stuff.
+ continue;
+ }
+
+ List lsParents = new List();
+ bool bStillGoing = true;
+ var directory = item.Directory;
+ while (bStillGoing)
+ {
+ if (String.Equals(directory.Name, "ProtocolTests")
+ || directory.Parent == null)
+ {
+ bStillGoing = false;
+ }
+ else if (asExcludeDirectories.Contains(directory.Name))
+ {
+ lsParents.Clear();
+ bStillGoing = false;
+ }
+ else
+ {
+ lsParents.Add(directory.Name);
+ directory = directory.Parent;
+ }
+ }
+
+ if (lsParents.Count == 0)
+ {
+ continue;
+ }
+
+ lsParents.Reverse();
+
+ string newNamespace = String.Join(".", lsParents);
+
+ if (!namespaces.Contains(newNamespace))
+ {
+ lsWrongFiles.Add(String.Join("/", lsParents));
+ }
+ }
+
+ lsWrongFiles.Should().BeEmpty();
+ }
+
+ [TestMethod]
+ public void CheckNamespacesOfTests()
+ {
+ // Get all namespaces
+ var namespaces = Assembly
+ .GetAssembly(typeof(Validator))
+ .GetTypes()
+ .Where(x => !String.IsNullOrWhiteSpace(x.Namespace) && x.Namespace.StartsWith("SLDisValidator2.Tests"))
+ .Select(x => x.Namespace.Replace("SLDisValidator2.Tests.", String.Empty));
+
+ var currentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
+ string solutiondir = Directory.GetParent(currentDirectory).Parent.Parent.Parent.FullName;
+
+ string newPath = Path.Combine(solutiondir, "Protocol", "Tests");
+
+ // Get all Files
+ var files = Directory
+ .GetFiles(newPath, "*.cs", SearchOption.AllDirectories);
+
+ List lsWrongFiles = new List();
+
+ string[] asExcludeDirectories = new string[]
+ {
+ "bin", "obj"
+ };
+
+ foreach (var temp in files)
+ {
+ var item = new FileInfo(temp);
+
+ List lsParents = new List
+ {
+ item.Name.Replace(".cs", String.Empty)
+ };
+ bool bStillGoing = true;
+ var directory = item.Directory;
+ while (bStillGoing)
+ {
+ if (String.Equals(directory.Name, "Tests"))
+ {
+ bStillGoing = false;
+ }
+ else if (asExcludeDirectories.Contains(directory.Name))
+ {
+ lsParents.Clear();
+ bStillGoing = false;
+ }
+ else
+ {
+ lsParents.Add(directory.Name);
+ directory = directory.Parent;
+ }
+ }
+
+ if (lsParents.Count == 1)
+ {
+ continue;
+ }
+
+ lsParents.Reverse();
+ string newNamespace = String.Join(".", lsParents);
+
+ if (!namespaces.Contains(newNamespace))
+ {
+ lsWrongFiles.Add(String.Join("/", lsParents));
+ }
+ }
+
+ lsWrongFiles.Should().BeEmpty();
+ }
+
+ ///
+ /// Checks if the unit tests don't have failing QActions.
+ /// Will also fail if any have MinDmaVersion tag in the compliancies (currently it isn't possible to validate newer C#)
+ ///
+ [TestMethod]
+ public void CheckQActionCompilation()
+ {
+ var test = new SLDisValidator2.Tests.Protocol.QActions.QAction.CSharpQActionCompilation.CSharpQActionCompilation();
+
+ // Search for Protocol directory which is the root folder of all unit tests
+ if (!Files.TryGetProtocolDirectory(out DirectoryInfo protocolDirectory))
+ {
+ Assert.Fail("Protocol folder not found.");
+ }
+
+ // Get all XML files
+ List<(string fileName, string fileLocation, Generic.TestType testType)> allFiles = protocolDirectory
+ .GetFiles("*.xml", SearchOption.AllDirectories)
+ .Select(GetFileData)
+ .ToList();
+
+ allFiles.Should().NotBeEmpty();
+
+ List failedTests = new List();
+ foreach ((string fileName, string fileLocation, Generic.TestType testType) in allFiles)
+ {
+ if (fileName == null || fileLocation.Contains("CSharpQActionCompilation"))
+ {
+ continue;
+ }
+
+ try
+ {
+ Generic.ValidateData data = new Generic.ValidateData
+ {
+ ExpectedResults = new List(),
+ FileName = fileName,
+ TestType = testType,
+ };
+
+ Generic.Validate(test, data, fileLocation);
+ }
+ catch (AssertFailedException /*afEx*/)
+ {
+ Regex r = new Regex(@".*\\[SLDisValidatorUnitTests]+\\(.*)");
+
+ var location = r.Match(fileLocation).Groups[1].Value;
+
+ //failedTests.Add($"{location}{testType}\\{fileName}|\n{afex.Message}");
+ failedTests.Add($"{location}{testType}\\{fileName}");
+ }
+ catch (Exception ex)
+ {
+ failedTests.Add($"EXCEPTION: {ex.Message}");
+ }
+ }
+
+ if (failedTests.Any())
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.AppendLine();
+ foreach (var item in failedTests)
+ {
+ sb.AppendLine($"=> {item}");
+ }
+
+ Assert.Fail(sb.ToString());
+ }
+
+ (string, string, Generic.TestType) GetFileData(FileInfo fileInfo)
+ {
+ Generic.TestType testType = Generic.TestType.Invalid;
+ DirectoryInfo directory = fileInfo.Directory;
+ while (!String.Equals(directory.Name, "Samples"))
+ {
+ if (String.Equals(directory.Name, "Compare") || String.Equals(directory.Name, "Codefix", StringComparison.OrdinalIgnoreCase))
+ {
+ return (null, null, testType);
+ }
+
+ if (String.Equals(directory.Name, "Valid"))
+ {
+ testType = Generic.TestType.Valid;
+ }
+
+ directory = directory.Parent;
+ }
+
+ return (fileInfo.Name, $"{directory.Parent.FullName}\\", testType);
+ }
+ }
+ }
+
+ [TestClass]
+ public class BasicChecks
+ {
+ ///
+ /// Will fail when a check throws an error/exception when the protocol tag or Params, Groups, Triggers, ... is missing
+ ///
+ [TestMethod]
+ public void CheckBasics()
+ {
+ // Get all tests
+ var allTests = TestCollector.GetAllValidateTests().Tests;
+
+ var currentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
+ string solutiondir = Directory.GetParent(currentDirectory).Parent.Parent.FullName;
+
+ // Need to add the backslashes as it doesn't recognize it as a directory.
+ string fileLocation = Path.Combine(solutiondir, "Generic Tests\\");
+
+ List failedTests = new List();
+ foreach ((IValidate test, TestAttribute attribute) in allTests)
+ {
+ if (attribute.CheckId == 1 /* Protocol Tag itself */ && attribute.Category == Category.Protocol)
+ {
+ continue;
+ }
+
+ try
+ {
+ // No Protocol Tag
+ Generic.ValidateData data = new Generic.ValidateData
+ {
+ ExpectedResults = new List(),
+ FileName = "NoProtocol",
+ TestType = Generic.TestType.Valid,
+ };
+
+ Generic.Validate(test, data, fileLocation);
+
+ if (attribute.Category > Category.Protocol)
+ {
+ // No Params, Groups, Triggers, Actions, Timers, ...
+ Generic.ValidateData data2 = new Generic.ValidateData
+ {
+ ExpectedResults = new List(),
+ FileName = "NoSubChildren",
+ TestType = Generic.TestType.Valid,
+ };
+
+ Generic.Validate(test, data2, fileLocation);
+ }
+ }
+ catch (AssertFailedException)
+ {
+ failedTests.Add($"{Convert.ToString(attribute.Category)}.{test.GetType().Name}");
+ }
+ catch (NullReferenceException)
+ {
+ failedTests.Add($"NULL REF: {Convert.ToString(attribute.Category)}.{test.GetType().Name}");
+ }
+ catch (Exception e)
+ {
+ failedTests.Add($"EXCEPTION: {attribute.Category}.{test.GetType().Name} ({e.Message})");
+ }
+ }
+
+ failedTests.Should().BeEmpty();
+ }
+
+ [TestMethod]
+ public void ValidatorChecksShouldNotStoreData()
+ {
+ var tests = TestCollector.GetAllValidateTests().Tests;
+
+ foreach ((IValidate test, TestAttribute _) in tests)
+ {
+ var type = test.GetType();
+
+ var fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
+ foreach (var fi in fields)
+ {
+ // Exception for Regex
+ if (fi.FieldType == typeof(Regex))
+ {
+ continue;
+ }
+
+ bool isConstant = fi.IsLiteral && !fi.IsInitOnly;
+ if (!isConstant)
+ {
+ Assert.Fail("Check contains fields: " + type.FullName);
+ }
+ }
+
+ var props = type.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
+ if (props.Any())
+ {
+ Assert.Fail("Check contains properties: " + type.FullName);
+ }
+ }
+ }
+ }
+
+ [TestClass]
+ [Ignore("TODO: Fix")]
+ public class RoslynUnitTests
+ {
+ private static Solution solution;
+ private static Project valProject;
+ private static Project testProject;
+ private static Compilation compilationVal2;
+ private static Compilation compilationUnitTest;
+
+ [ClassInitialize]
+ public static void ClassInit(TestContext context)
+ {
+ solution = Roslyn.GetSolution();
+
+ valProject = solution.Projects.Single(x => x.Name == "Protocol");
+ testProject = solution.Projects.Single(x => x.Name == "ProtocolTests(net472)");
+
+ // SDK style projects don't have each file mentioned in the csproj anymore
+ valProject = valProject.WithAllSourceFiles();
+ testProject = testProject.WithAllSourceFiles();
+
+ compilationVal2 = valProject.GetCompilationAsync().Result;
+ compilationUnitTest = testProject.GetCompilationAsync().Result;
+
+ compilationVal2.SyntaxTrees.Should().NotBeEmpty();
+ compilationUnitTest.SyntaxTrees.Should().NotBeEmpty();
+ }
+
+ [ClassCleanup]
+ public static void ClassClean()
+ {
+ solution = null;
+
+ valProject = null;
+ testProject = null;
+
+ compilationVal2 = null;
+ compilationUnitTest = null;
+ }
+
+ [TestMethod]
+ [Ignore("TODO")]
+ public void Testing()
+ {
+ List errors = new List();
+
+ foreach (var tree in compilationVal2.SyntaxTrees)
+ {
+ if (!tree.FilePath.Contains(Path.Combine("SLDisValidator2", "Tests")))
+ {
+ continue;
+ }
+
+ // Get Root
+ var rootSyntaxNode = tree.GetRootAsync().Result;
+ var analyzer = new TestAnalyzer(errors);
+
+ RoslynVisitor visitor = new RoslynVisitor(analyzer);
+ visitor.Visit(rootSyntaxNode);
+ }
+
+ if (errors.Count > 0)
+ {
+ Assert.Fail(String.Join(Environment.NewLine, errors));
+ }
+ }
+
+ private class TestAnalyzer : CSharpAnalyzerBase
+ {
+ private readonly List errors;
+
+ public TestAnalyzer(List errors)
+ {
+ this.errors = errors;
+ }
+
+ public override void CheckClass(ClassClass classClass)
+ {
+ if (CoverCheckClass(classClass))
+ {
+ return;
+ }
+
+ if (classClass.Access == AccessModifier.Public)
+ {
+ errors.Add($"'{classClass.Name}' has public access but shouldn't have.");
+ }
+ }
+
+ private bool CoverCheckClass(ClassClass classClass)
+ {
+ if (!classClass.Name.StartsWith("Check") && !classClass.Name.StartsWith("CSharp"))
+ {
+ return false;
+ }
+
+ if (classClass.Access != AccessModifier.Public)
+ {
+ errors.Add($"'{classClass.Name}' has no public access.");
+ }
+
+ if (classClass.Attributes.All(attribute => attribute.Name != "Test"))
+ {
+ errors.Add($"'{classClass.Name}' has no {nameof(TestAttribute)}.");
+ }
+
+ string[] interfaces =
+ {
+ nameof(IValidate),
+ nameof(ICodeFix),
+ nameof(ICompare)
+ };
+
+ if (!classClass.InheritanceItems.Any(x => interfaces.Contains(x)))
+ {
+ errors.Add($"'{classClass.Name}' has no valid interfaces.");
+ }
+
+ string[] methods = new[]
+ {
+ nameof(IValidate.Validate),
+ nameof(ICodeFix.Fix),
+ nameof(ICompare.Compare)
+ };
+
+ var remainingMethods = classClass.Methods.Where(methodClass => !methods.Contains(methodClass.Name))
+ .Select(methodClass => methodClass.Name)
+ .ToList();
+ if (remainingMethods.Any())
+ {
+ errors.Add($"'{classClass.Name}' has extra methods: {String.Join(", ", remainingMethods)}.");
+ }
+
+ return true;
+ }
+ }
+
+ ///
+ /// Check if all the unit tests are correct.
+ /// Will fail when an unit test is encountered that doesn't have any ExpectedResults.
+ /// Will fail when an unit test is encountered that has results, but is ignored.
+ /// Will fail when an unit test is encountered without the TestMethod attribute.
+ ///
+ [TestMethod]
+ public void CheckUnitTests()
+ {
+ HashSet ignoredTests = new HashSet();
+ HashSet testsWithoutAttribute = new HashSet();
+ HashSet emptyTests = new HashSet();
+
+ foreach (var tree in compilationUnitTest.SyntaxTrees)
+ {
+ if (!tree.FilePath.Contains(Path.Combine("SLDisValidatorUnitTests", "Protocol")))
+ {
+ continue;
+ }
+
+ // Get Root
+ var rootSyntaxNode = tree.GetRootAsync().Result;
+
+ // Get Namespace
+ string ns = Roslyn.GetNamespace(rootSyntaxNode);
+
+ // Remove the first and second part of the namespace
+ ns = ns.Replace("SLDisValidatorUnitTests.", String.Empty);
+
+ // Find Validate/Compare/CodeFix Class
+ foreach (ClassDeclarationSyntax @class in rootSyntaxNode.GetClasses("Validate", "Compare", "CodeFix"))
+ {
+ // Find all the UnitTest Methods
+ foreach (MethodDeclarationSyntax method in @class.GetMethods())
+ {
+ bool isIgnored = false;
+ bool? isIgnoredWithReason = null;
+ bool isTestMethod = false;
+ bool hasErrorMessages = false;
+ bool isInvalidType = false;
+
+ // Check Attributes
+ foreach (AttributeSyntax attr in method.GetAttributes())
+ {
+ switch (attr.Name.ToString())
+ {
+ case "TestMethod":
+ case "DataTestMethod":
+ isTestMethod = true;
+ break;
+
+ case "Ignore":
+ isIgnored = attr.ArgumentList?.Arguments.Count != 1;
+ isIgnoredWithReason = !isIgnored;
+ break;
+ }
+ }
+
+ // Check if using ErrorMessages
+ foreach (var item in method.DescendantNodes().OfType())
+ {
+ var left = item.Left.ToString();
+ var right = item.Right.ToString();
+
+ if (String.Equals(left, "ExpectedResults") && item.Right is ObjectCreationExpressionSyntax list && list.Initializer?.Expressions.Any() == true)
+ {
+ hasErrorMessages = true;
+ }
+ else if (String.Equals(left, "TestType") && String.Equals(right, "Generic.TestType.Invalid"))
+ {
+ isInvalidType = true;
+ }
+ }
+
+ if (isTestMethod)
+ {
+ if (isIgnoredWithReason.GetValueOrDefault())
+ {
+ // Test is ignored for a reason. This needs to be checked manual.
+ continue;
+ }
+
+ if (hasErrorMessages && isIgnored)
+ {
+ ignoredTests.Add($"{ns}.{method.Identifier.Text}");
+ }
+
+ if (!hasErrorMessages && !isIgnored && isInvalidType)
+ {
+ emptyTests.Add($"{ns}.{method.Identifier.Text}");
+ }
+
+ // Doesn't have error messages and is ignored => New tests
+ }
+ else
+ {
+ testsWithoutAttribute.Add($"{ns}.{method.Identifier.Text}");
+ }
+ }
+ }
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ if (ignoredTests.Count > 0)
+ {
+ sb
+ .AppendLine($"{ignoredTests.Count} ignored UnitTests with ErrorMessages:")
+ .AppendLine(String.Join(Environment.NewLine, ignoredTests))
+ .AppendLine();
+ }
+
+ if (testsWithoutAttribute.Count > 0)
+ {
+ sb
+ .AppendLine($"{testsWithoutAttribute.Count} UnitTests without TestMethod attribute:")
+ .AppendLine(String.Join(Environment.NewLine, testsWithoutAttribute))
+ .AppendLine();
+ }
+
+ if (emptyTests.Count > 0)
+ {
+ sb
+ .AppendLine($"{emptyTests.Count} UnitTests without ErrorMessages:")
+ .AppendLine(String.Join(Environment.NewLine, emptyTests));
+ }
+
+ string message = sb.ToString().Trim();
+ if (!String.IsNullOrWhiteSpace(message))
+ {
+ Assert.Fail($"{Environment.NewLine}{message}");
+ }
+ }
+
+ ///
+ /// Checks if all the error messages are being used in an unit test.
+ /// Will fail when an error message is encountered that has no reference in an unit test.
+ /// Will fail when an error message is encountered that has no references at all. (Needs to be improved on)
+ /// TODO: Expand so it checks the ErrorMessages class.
+ ///
+ [TestMethod]
+ public void CheckErrorMessages()
+ {
+ HashSet unusedErrorMessages = new HashSet();
+ HashSet untestedErrorMessages = new HashSet();
+
+ foreach (var tree in compilationVal2.SyntaxTrees)
+ {
+ if (!tree.FilePath.Contains(Path.Combine("SLDisValidator2", "Error Messages")))
+ {
+ continue;
+ }
+
+ // Get Semantic Model
+ var semantic = compilationVal2.GetSemanticModel(tree);
+
+ // Get Root
+ var rootSyntaxNode = tree.GetRootAsync().Result;
+
+ // Get Namespace
+ string ns = Roslyn.GetNamespace(rootSyntaxNode);
+
+ // Remove the first and second part of the namespace (So it starts with Protocol.)
+ ns = ns.Replace("SLDisValidator2.Tests.", String.Empty);
+
+ string expectedClassName = ns.Substring(ns.LastIndexOf('.') + 1);
+
+ // Find Error Class
+ foreach (ClassDeclarationSyntax @class in rootSyntaxNode.GetClasses("Error"))
+ {
+ // Find all the ErrorMessage Methods
+ foreach (MethodDeclarationSyntax method in @class.GetMethods())
+ {
+ ISymbol symbol = Roslyn.GetSymbol(semantic, method);
+ var callers = SymbolFinder.FindCallersAsync(symbol, solution).Result;
+
+ foreach (var reference in callers)
+ {
+ string errorMessageFullPath = $"{ns}.{method.Identifier.Text}";
+
+ if (!reference.Locations.Any())
+ {
+ // Check if corresponding class has TestAttribute
+ // Find class in the same namespace with the correct name and see if it has correct attribute(s)
+ ClassDeclarationSyntax checkClass = Roslyn.FindClass(compilationVal2, ns, expectedClassName);
+
+ if (checkClass == null)
+ {
+ unusedErrorMessages.Add(errorMessageFullPath);
+ }
+ else
+ {
+ // Find TestAttribute
+ bool hasTestAttribute = false;
+ foreach (AttributeSyntax attr in checkClass.GetAttributes())
+ {
+ switch (attr.Name.ToString())
+ {
+ case "Test":
+ hasTestAttribute = true;
+ break;
+ }
+ }
+
+ if (hasTestAttribute)
+ {
+ unusedErrorMessages.Add(errorMessageFullPath);
+ }
+ }
+ }
+
+ foreach (var location in reference.Locations)
+ {
+ // Check if reference is in UnitTest project
+ if (!reference.CallingSymbol.ContainingAssembly.Name.StartsWith(testProject.DefaultNamespace))
+ {
+ continue;
+ }
+
+ var testMethods = location.SourceTree.GetRoot().DescendantNodes(location.SourceSpan).OfType().ToList();
+
+ foreach (MethodDeclarationSyntax testMethod in testMethods)
+ {
+ // Check if it has TestMethod attribute and not the Ignore attribute?
+ bool isTestMethod = false;
+ bool isIgnored = false;
+
+ foreach (AttributeSyntax attr in testMethod.GetAttributes())
+ {
+ switch (attr.Name.ToString())
+ {
+ case "TestMethod":
+ case "DataTestMethod":
+ isTestMethod = true;
+ break;
+
+ case "Ignore":
+ // Ignore with argument means that there is a reason why it's ignored.
+ // These need to be looked at manually.
+ isIgnored = attr.ArgumentList?.Arguments.Count != 1;
+ break;
+ }
+ }
+
+ if (isIgnored || !isTestMethod)
+ {
+ untestedErrorMessages.Add(errorMessageFullPath);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ if (untestedErrorMessages.Count > 0)
+ {
+ sb
+ .AppendLine($"{untestedErrorMessages.Count} ErrorMessages that aren't tested:")
+ .AppendLine(String.Join(Environment.NewLine, untestedErrorMessages))
+ .AppendLine();
+ }
+
+ if (unusedErrorMessages.Count > 0)
+ {
+ sb
+ .AppendLine($"{unusedErrorMessages.Count} ErrorMessages that aren't used anywhere:")
+ .AppendLine(String.Join(Environment.NewLine, unusedErrorMessages));
+ }
+
+ string message = sb.ToString().Trim();
+ if (!String.IsNullOrWhiteSpace(message))
+ {
+ Assert.Fail($"{Environment.NewLine}{message}");
+ }
+ }
+
+ ///
+ /// Checks the checks themselves.
+ /// Will fail when a check is encountered that has no Test attribute but has one or more error messages.
+ /// Will fail when a check is encountered that doesn't match the name from the namespace.
+ /// [Disabled] Will fail when a check is encountered that has a Test attribute but has no error messages.
+ ///
+ [TestMethod]
+ public void CheckChecks()
+ {
+ HashSet inactiveChecks = new HashSet();
+ HashSet wrongNamedClasses = new HashSet();
+ HashSet emptyChecks = new HashSet();
+
+ foreach (var tree in compilationVal2.SyntaxTrees)
+ {
+ if (!tree.FilePath.Contains(Path.Combine("Protocol", "Tests")))
+ {
+ continue;
+ }
+
+ // Get Root
+ var rootSyntaxNode = tree.GetRootAsync().Result;
+
+ // Get Namespace
+ string ns = Roslyn.GetNamespace(rootSyntaxNode);
+
+ // Remove the first and second part of the namespace (So it starts with Protocol.)
+ ns = ns.Replace("SLDisValidator2.Tests.", String.Empty);
+
+ string expectedClassName = ns.Substring(ns.LastIndexOf('.') + 1);
+
+ // Find Check Class
+ bool foundClass = false;
+ foreach (ClassDeclarationSyntax @class in rootSyntaxNode.GetClasses(expectedClassName))
+ {
+ foundClass = true;
+
+ // See if any have the TestAttribute
+ bool hasTestAttribute = false;
+ foreach (AttributeSyntax attr in @class.GetAttributes())
+ {
+ switch (attr.Name.ToString())
+ {
+ case "Test":
+ hasTestAttribute = true;
+ break;
+ }
+ }
+
+ bool hasMessages = false;
+ foreach (var node in @class.DescendantNodes().OfType())
+ {
+ if (node.Expression is MemberAccessExpressionSyntax maes && maes.Expression is IdentifierNameSyntax ins &&
+ (ins.Identifier.Text == "Error" || ins.Identifier.Text == "ErrorCompare"))
+ {
+ hasMessages = true;
+ break;
+ }
+ }
+
+ if (hasMessages && !hasTestAttribute)
+ {
+ inactiveChecks.Add(ns);
+ }
+
+ //if (!hasMessages && hasTestAttribute)
+ //{
+ // emptyChecks.Add(ns);
+ //}
+ }
+
+ if (!foundClass)
+ {
+ wrongNamedClasses.Add(ns);
+ }
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ if (inactiveChecks.Count > 0)
+ {
+ sb
+ .AppendLine($"{inactiveChecks.Count} Inactive Checks:")
+ .AppendLine(String.Join(Environment.NewLine, inactiveChecks))
+ .AppendLine();
+ }
+
+ if (emptyChecks.Count > 0)
+ {
+ sb
+ .AppendLine($"{emptyChecks.Count} Empty Checks:")
+ .AppendLine(String.Join(Environment.NewLine, emptyChecks))
+ .AppendLine();
+ }
+
+ if (wrongNamedClasses.Count > 0)
+ {
+ sb
+ .AppendLine($"{wrongNamedClasses.Count} WrongNamed Checks:")
+ .AppendLine(String.Join(Environment.NewLine, wrongNamedClasses));
+ }
+
+ string message = sb.ToString().Trim();
+ if (!String.IsNullOrWhiteSpace(message))
+ {
+ Assert.Fail($"{Environment.NewLine}{message}");
+ }
+ }
+
+ ///
+ /// Checks if there should be a code fix and checks if there is an unit test for it.
+ ///
+ [TestMethod]
+ [Ignore("Currently this one is giving errors (fix is ignored) on CodeFixes that aren't implemented yet.")]
+ public void CheckUnitTestsForCodeFix()
+ {
+ HashSet ignoredUnitTest = new HashSet();
+ HashSet notUnitTest = new HashSet();
+ HashSet messagesNotTested = new HashSet();
+
+ var docsUnitTest = testProject.Documents.ToImmutableHashSet();
+ foreach (var tree in compilationVal2.SyntaxTrees)
+ {
+ if (!tree.FilePath.Contains(Path.Combine("Protocol", "Error Messages")))
+ {
+ continue;
+ }
+
+ // Get Semantic Model
+ var semantic = compilationVal2.GetSemanticModel(tree);
+
+ // Get Root
+ var rootSyntaxNode = tree.GetRootAsync().Result;
+
+ // Get Namespace
+ string ns = Roslyn.GetNamespace(rootSyntaxNode);
+
+ // Remove the first and second part of the namespace
+ ns = ns.Replace("SLDisValidator2.Tests.", String.Empty);
+
+ // Find Error Class
+ foreach (ClassDeclarationSyntax @class in rootSyntaxNode.GetClasses("Error"))
+ {
+ // Find all the UnitTest Methods
+ foreach (MethodDeclarationSyntax method in @class.GetMethods())
+ {
+ if (!CodeFixRoslyn.HasCodeFix(method))
+ {
+ continue;
+ }
+
+ ISymbol symbol = Roslyn.GetSymbol(semantic, method);
+
+ // Only search in UnitTests
+ var references = SymbolFinder.FindReferencesAsync(symbol, solution, docsUnitTest).Result;
+
+ foreach (var reference in references)
+ {
+ if (!reference.Locations.Any())
+ {
+ continue;
+ }
+
+ foreach (var location in reference.Locations)
+ {
+ var unitTestMethods = location.Location.SourceTree.GetRoot().DescendantNodesAndSelf(location.Location.SourceSpan).OfType();
+
+ var semanticUnitTest = compilationUnitTest.GetSemanticModel(location.Location.SourceTree);
+
+ // Prepare list with current implemented code fixes.
+ List existingCodeFixes = new List();
+ foreach (ClassDeclarationSyntax unitTestClass in location.Location.SourceTree.GetRoot().GetClasses())
+ {
+ if (!String.Equals(unitTestClass.Identifier.Text, "CodeFix"))
+ {
+ continue;
+ }
+
+ foreach (MethodDeclarationSyntax codeFixMethod in unitTestClass.GetMethods())
+ {
+ bool isNewWay = codeFixMethod.ExpressionBody == null;
+
+ string fileName = null;
+
+ if (isNewWay)
+ {
+ foreach (var item in codeFixMethod.DescendantNodes().OfType())
+ {
+ var left = item.Left.ToString();
+
+ if (String.Equals(left, "FileNameBase"))
+ {
+ fileName = item.Right.ToString().Replace("\"", String.Empty).Replace(".xml", String.Empty);
+ break;
+ }
+ }
+ }
+ else
+ {
+ // FileName is based on name
+ fileName = codeFixMethod.Identifier.Text.Split('_').Last();
+ }
+
+ bool isTestMethod = false;
+ bool isIgnored = false;
+
+ foreach (AttributeSyntax attr in codeFixMethod.GetAttributes())
+ {
+ var name = semanticUnitTest.GetTypeInfo(attr).Type.Name;
+
+ switch (name)
+ {
+ case "TestMethod":
+ case "DataTestMethod":
+ isTestMethod = true;
+ break;
+
+ case "Ignore":
+ // Ignore with argument means that there is a reason why it's ignored.
+ // These need to be looked at manually.
+ isIgnored = attr.ArgumentList?.Arguments.Count != 1;
+ break;
+ }
+
+ if (isIgnored && isTestMethod)
+ {
+ ignoredUnitTest.Add(codeFixMethod.Identifier.Text);
+ }
+
+ if (isTestMethod)
+ {
+ existingCodeFixes.Add(fileName);
+ }
+ else
+ {
+ notUnitTest.Add(codeFixMethod.Identifier.Text);
+ }
+ }
+ }
+ }
+
+ foreach (MethodDeclarationSyntax testMethod in unitTestMethods)
+ {
+ bool isNewWay = testMethod.ExpressionBody == null;
+
+ string fileName = null;
+
+ if (isNewWay)
+ {
+ foreach (var item in testMethod.DescendantNodes().OfType())
+ {
+ var left = item.Left.ToString();
+
+ if (String.Equals(left, "FileName"))
+ {
+ fileName = item.Right.ToString().Replace("\"", String.Empty).Replace(".xml", String.Empty);
+ break;
+ }
+ }
+ }
+ else
+ {
+ // FileName is based on name
+ fileName = testMethod.Identifier.Text.Split('_').Last();
+ }
+
+ // Check if CodeFix unittest exists with that FileName as name or inside the method
+ if (!existingCodeFixes.Contains(fileName))
+ {
+ string text = $"{ns}.{method.Identifier.Text}";
+ messagesNotTested.Add(text);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ if (notUnitTest.Count > 0)
+ {
+ // Error messages that are being used in the Unittest project, but not in a test method.
+ sb.AppendLine("CodeFix Methods without [TestMethod] attribute:")
+ .AppendLine(String.Join(Environment.NewLine, notUnitTest))
+ .AppendLine();
+ }
+
+ if (ignoredUnitTest.Count > 0)
+ {
+ // Testmethods with Error messages that are ignored.
+ sb.AppendLine("Ignored CodeFix Test methods that have a CodeFix:")
+ .AppendLine(String.Join(Environment.NewLine, ignoredUnitTest))
+ .AppendLine();
+ }
+
+ if (messagesNotTested.Count > 0)
+ {
+ sb.AppendLine("CodeFix error messages that aren't unittested yet:")
+ .AppendLine(String.Join(Environment.NewLine, messagesNotTested));
+ }
+
+ string result = sb.ToString().Trim();
+ if (!String.IsNullOrWhiteSpace(result))
+ {
+ Assert.Fail($"{Environment.NewLine}{result}");
+ }
+ }
+
+ ///
+ /// Checks if the .xml files in the project are used in UnitTests or not.
+ /// Will fail when an unit test is encountered that doesn't have a file.
+ /// Will fail when a file is encountered that doesn't have an unit test.
+ ///
+ [TestMethod]
+ public void CheckFiles()
+ {
+ List missingFiles = new List();
+ List foundFiles = new List();
+
+ // Search for Protocol directory which is the root folder of all unit tests
+ if (!Files.TryGetProtocolDirectory(out DirectoryInfo protocolDirectory))
+ {
+ Assert.Fail("Protocol folder not found.");
+ }
+
+ // Get all XML files
+ List allFiles = protocolDirectory
+ .GetFiles("*.xml", SearchOption.AllDirectories)
+ .Where(x => !x.Name.EndsWith("_Results.xml")) // Maybe add .xml? Need to check
+ .Select(x => x.FullName.Replace($"{protocolDirectory.Parent.FullName}\\", String.Empty)) // Remove C://... part. Only keep the part starting with Protocol
+ .ToList();
+
+ allFiles.Should().NotBeEmpty();
+
+ foreach (var tree in compilationUnitTest.SyntaxTrees)
+ {
+ if (!tree.FilePath.Contains(Path.Combine("ProtocolTests", "Protocol")))
+ {
+ continue;
+ }
+
+ // Get Root
+ var rootSyntaxNode = tree.GetRootAsync().Result;
+
+ // Get Namespace
+ string ns = Roslyn.GetNamespace(rootSyntaxNode);
+
+ // Remove the first part of the namespace
+ ns = ns.Replace("SLDisValidatorUnitTests.", String.Empty);
+ string nsFolder = ns.Replace('.', '\\');
+
+ // Find Validate/Compare/CodeFix Class
+ foreach (ClassDeclarationSyntax @class in rootSyntaxNode.GetClasses("Validate", "Compare", "CodeFix"))
+ {
+ bool isValidate = String.Equals(@class.Identifier.Text, "Validate");
+ bool isCompare = String.Equals(@class.Identifier.Text, "Compare");
+ bool isCodeFix = String.Equals(@class.Identifier.Text, "CodeFix");
+
+ // Find all the UnitTest Methods
+ IEnumerable methodsInClass = @class.DescendantNodes().OfType().ToList();
+ methodsInClass.Should().NotBeEmpty();
+ foreach (MethodDeclarationSyntax method in methodsInClass)
+ {
+ string testCategory = String.Empty;
+ bool isNewWay = method.ExpressionBody == null;
+
+ // Check Attributes
+ foreach (AttributeSyntax attr in method.GetAttributes())
+ {
+ switch (attr.Name.ToString())
+ {
+ case "TestCategory":
+ testCategory = attr.ArgumentList.Arguments[0].ToString().Replace("\"", String.Empty);
+ break;
+ }
+ }
+
+ string fileName = String.Empty;
+ if (isNewWay)
+ {
+ foreach (var item in method.DescendantNodes().OfType())
+ {
+ var left = item.Left.ToString();
+
+ if (String.Equals(left, "FileName") || String.Equals(left, "FileNameBase"))
+ {
+ fileName = item.Right.ToString().Replace("\"", String.Empty);
+ break;
+ }
+ else if (String.Equals(left, "TestType"))
+ {
+ testCategory = item.Right.ToString().Replace("Generic.TestType.", String.Empty);
+ }
+ }
+ }
+ else
+ {
+ // FileName is based on name
+ fileName = method.Identifier.Text.Split('_').Last();
+ }
+
+ // Check extension
+ if (isValidate)
+ {
+ fileName = fileName.EndsWith(".xml") ? fileName : $"{fileName}.xml";
+
+ // Create full path
+ string fullFilePath = Path.Combine(nsFolder, "Samples", @class.Identifier.Text, testCategory, fileName);
+
+ if (allFiles.Contains(fullFilePath))
+ {
+ foundFiles.Add(fullFilePath);
+ }
+ else
+ {
+ // File doesn't exists
+ missingFiles.Add(fullFilePath);
+ }
+ }
+ else if (isCompare)
+ {
+ string oldFile = $"{fileName}_Old.xml";
+ string newFile = $"{fileName}_New.xml";
+
+ // Create full path
+ string fullFilePathOld = Path.Combine(nsFolder, "Samples", @class.Identifier.Text, testCategory, oldFile);
+ string fullFilePathNew = Path.Combine(nsFolder, "Samples", @class.Identifier.Text, testCategory, newFile);
+
+ if (allFiles.Contains(fullFilePathOld))
+ {
+ foundFiles.Add(fullFilePathOld);
+ }
+ else
+ {
+ // File doesn't exists
+ missingFiles.Add(fullFilePathOld);
+ }
+
+ if (allFiles.Contains(fullFilePathNew))
+ {
+ foundFiles.Add(fullFilePathNew);
+ }
+ else
+ {
+ // File doesn't exists
+ missingFiles.Add(fullFilePathNew);
+ }
+ }
+ else if (isCodeFix)
+ {
+ fileName = fileName.EndsWith(".xml") ? fileName : $"{fileName}.xml";
+
+ // Create full path
+ string fullFilePath = Path.Combine(nsFolder, "Samples", @class.Identifier.Text, fileName);
+
+ if (String.Equals(@class.Identifier.Text, "CodeFix"))
+ {
+ fullFilePath = fullFilePath.Replace("CodeFix", "Codefix");
+ }
+
+ if (allFiles.Contains(fullFilePath))
+ {
+ foundFiles.Add(fullFilePath);
+ }
+ else
+ {
+ // File doesn't exists
+ missingFiles.Add(fullFilePath);
+ }
+ }
+ }
+ }
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ if (missingFiles.Count > 0)
+ {
+ sb.AppendLine($"{missingFiles.Count} Files that are used in unit tests but don't exist:")
+ .AppendLine(String.Join(Environment.NewLine, missingFiles))
+ .AppendLine();
+ }
+
+ List excessiveFiles = allFiles.Except(foundFiles).ToList();
+
+ if (excessiveFiles.Count > 0)
+ {
+ sb.AppendLine($"{excessiveFiles.Count} Files that aren't used in any unit test:")
+ .AppendLine(String.Join(Environment.NewLine, excessiveFiles));
+ }
+
+ string result = sb.ToString().Trim();
+ if (!String.IsNullOrWhiteSpace(result))
+ {
+ Assert.Fail($"{Environment.NewLine}{result}");
+ }
+ }
+
+ ///
+ /// Checks if the .xml files in the project are using the correct XSD.
+ /// Will fail when a file doesn't have a XSD linked to it.
+ /// Will fail when a file doesn't have the correct XSD linked to it.
+ /// Will fail when a file has unknown tags or attributes
+ ///
+ [TestMethod]
+ public void CheckFiles_Xsd()
+ {
+ /*
+ * Different unit tests on which we don't want to validate via xsd:
+ * - Some use the 'Protocol/Connections' tag which we don't want to add to xsd because it should remain unknown to most developer but still needs to be used in some rare cases.
+ * - Some where we deliberately add xsd mistakes cause those also need to be covered by the Validator.
+ */
+ string[] filesToSkip =
+ {
+ @"Protocol\CheckConnections\Samples\Compare\Valid\Valid_Syntax1To2_New.xml",
+ @"Protocol\CheckConnections\Samples\Compare\Valid\Valid_Syntax1To3_New.xml",
+ @"Protocol\CheckConnections\Samples\Compare\Valid\Valid_Syntax2To1_Old.xml",
+ @"Protocol\CheckConnections\Samples\Compare\Valid\Valid_Syntax3To1_Old.xml",
+ @"Protocol\CheckConnections\Samples\Validate\Invalid\InvalidCombinationOfSyntax1And2.xml",
+ @"Protocol\CheckConnections\Samples\Validate\Invalid\UnrecommendedSyntax2.xml",
+ @"Protocol\Groups\Group\Content\CheckContentTag\Samples\Validate\Invalid\MixedTypes.xml",
+ @"Protocol\Triggers\Trigger\Content\Id\CheckIdTag\Samples\Validate\Invalid\MissingTag.xml",
+ @"Protocol\Type\CheckTypeTag\Samples\Validate\Valid\Valid_OtherSyntax.xml"
+ };
+
+ const string NAMESPACE = "http://www.skyline.be/validatorProtocolUnitTest";
+
+ List filesWithMissingXsd = new List();
+ List filesWithInvalidXsd = new List();
+ List filesWithXsdErrors = new List();
+
+ // Search for Protocol directory which is the root folder of all unit tests
+ if (!Files.TryGetProtocolDirectory(out DirectoryInfo protocolDirectory))
+ {
+ Assert.Fail("Protocol folder not found.");
+ }
+
+ // Get all XML files
+ List allFiles = protocolDirectory
+ .GetFiles("*.xml", SearchOption.AllDirectories)
+ .Select(x => x.FullName)
+ .ToList();
+
+ allFiles.Should().NotBeEmpty();
+
+ var xsds = protocolDirectory.Parent.GetFiles("*.xsd").ToList();
+
+ if (xsds.Count != 1)
+ {
+ Assert.Fail("Multiple XSD files found.");
+ }
+
+ var settings = new XmlReaderSettings();
+ settings.Schemas.Add("http://www.skyline.be/validatorProtocolUnitTest", xsds.First().FullName);
+ settings.ValidationType = ValidationType.Schema;
+ settings.ValidationEventHandler += Settings_ValidationEventHandler;
+
+ foreach (var filePath in allFiles)
+ {
+ string readablePath = filePath.Replace($"{protocolDirectory.Parent.FullName}\\", String.Empty);
+ if (readablePath == @"Protocol\CheckProtocolTag\Samples\Validate\Invalid\MissingTag.xml")
+ {
+ // Hasn't a Protocol tag, so no point in checking.
+ continue;
+ }
+
+ if (filesToSkip.Contains(readablePath))
+ {
+ continue;
+ }
+
+ (bool success, Stream stream) = Files.ReadTextFromFile(filePath);
+ if (!success)
+ {
+ Assert.Fail("Failed to retrieve the file: " + filePath);
+ }
+
+ try
+ {
+ using (var reader = XmlReader.Create(stream, settings))
+ {
+ while (reader.Read())
+ {
+ if (reader.Name != "Protocol" || reader.NodeType == XmlNodeType.EndElement)
+ {
+ // Let it continue the reading => Checking the XSD.
+ continue;
+ }
+
+ string ns = reader.NamespaceURI;
+
+ if (String.IsNullOrWhiteSpace(ns))
+ {
+ filesWithMissingXsd.Add(readablePath);
+ continue;
+ }
+
+ if (!String.Equals(ns, NAMESPACE))
+ {
+ filesWithInvalidXsd.Add(readablePath);
+ }
+ }
+ }
+ }
+ catch (InvalidDataException)
+ {
+ filesWithXsdErrors.Add(readablePath);
+ }
+ catch (Exception e)
+ {
+ filesWithMissingXsd.Add("BROKEN|" + e.Message + "|" + readablePath);
+ }
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ if (filesWithMissingXsd.Count > 0)
+ {
+ sb.AppendLine($"{filesWithMissingXsd.Count} Files that don't have an XSD:")
+ .AppendLine(" - " + String.Join(Environment.NewLine + " - ", filesWithMissingXsd))
+ .AppendLine();
+ }
+
+ if (filesWithInvalidXsd.Count > 0)
+ {
+ sb.AppendLine($"{filesWithInvalidXsd.Count} Files that have a wrong XSD:")
+ .AppendLine(" - " + String.Join(Environment.NewLine + " - ", filesWithInvalidXsd))
+ .AppendLine();
+ }
+
+ if (filesWithXsdErrors.Count > 0)
+ {
+ sb.AppendLine($"{filesWithXsdErrors.Count} Files that have XSD errors (unknown tags/attributes):")
+ .AppendLine(" - " + String.Join(Environment.NewLine + " - ", filesWithXsdErrors));
+ }
+
+ string result = sb.ToString().Trim();
+ if (!String.IsNullOrWhiteSpace(result))
+ {
+ Assert.Fail($"{Environment.NewLine}{result}");
+ }
+ }
+
+ private void Settings_ValidationEventHandler(object sender, ValidationEventArgs e)
+ {
+ string[] errorsToCatch = new[]
+ {
+ "has invalid child element", // Unknown tag
+ "attribute is not declared", // Unknown attribute
+ };
+
+ if (!errorsToCatch.Any(x => e.Message.Contains(x)))
+ {
+ return;
+ }
+
+ throw new InvalidDataException();
+ }
+ }
+
+ internal static class CodeFixRoslyn
+ {
+ internal static bool HasCodeFix(MethodDeclarationSyntax method)
+ {
+ foreach (var item in method.DescendantNodes().OfType())
+ {
+ var left = item.Left.ToString();
+
+ if (String.Equals(left, "HasCodeFix"))
+ {
+ Boolean.TryParse(item.Right.ToString(), out bool hasCodeFix);
+ return hasCodeFix;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ internal static class Roslyn
+ {
+ internal static IEnumerable GetClasses(this SyntaxNode node)
+ {
+ return node.DescendantNodes().OfType();
+ }
+
+ internal static IEnumerable GetClasses(this SyntaxNode node, params string[] classNames)
+ {
+ return node.DescendantNodes().OfType().Where(x => classNames.Contains(x.Identifier.Text));
+ }
+
+ internal static IEnumerable GetMethods(this ClassDeclarationSyntax @class)
+ {
+ return @class.DescendantNodes().OfType();
+ }
+
+ internal static IEnumerable GetAttributes(this MethodDeclarationSyntax method)
+ {
+ return method.DescendantNodes().OfType();
+ }
+
+ internal static IEnumerable GetAttributes(this ClassDeclarationSyntax method)
+ {
+ return method.DescendantNodes().OfType();
+ }
+
+ internal static Solution GetSolution()
+ {
+ try
+ {
+ string solutionPath = GetSolutionPath();
+
+ // Creating a build workspace.
+ var workspace = MSBuildWorkspace.Create();
+
+ // Opening the solution.
+ Solution solution = workspace.OpenSolutionAsync(solutionPath).Result;
+
+ return solution;
+ }
+ catch (ReflectionTypeLoadException tle)
+ {
+ string text = String.Join(";", tle.LoaderExceptions.Select(x => x.Message));
+ throw new Exception($"ReflectionTypeLoadException with these LoaderExceptions:{Environment.NewLine}{text}");
+ }
+ }
+
+ private static string GetSolutionPath()
+ {
+ string solutionPath = String.Empty;
+ DirectoryInfo a = Directory.GetParent(Assembly.GetExecutingAssembly().Location);
+ while (solutionPath == String.Empty)
+ {
+ var temp = a.GetDirectories("Skyline.DataMiner.CICD.Validators", SearchOption.TopDirectoryOnly);
+ if (temp.Length == 1)
+ {
+ var files = temp[0].GetFiles("*.sln");
+ if (files.Length > 0)
+ {
+ solutionPath = files[0].FullName;
+ }
+ else
+ {
+ a = a.Parent;
+ }
+ }
+ else
+ {
+ a = a.Parent;
+ }
+ }
+
+ return solutionPath;
+ }
+
+ internal static string GetNamespace(SyntaxNode rootSyntaxNode)
+ {
+ var temp = rootSyntaxNode.DescendantNodes().OfType().ToList();
+
+ if (temp == null || !temp.Any())
+ {
+ return String.Empty;
+ }
+
+ return temp[0].Name?.ToString() ?? String.Empty;
+ }
+
+ internal static ISymbol GetSymbol(SemanticModel semantic, MethodDeclarationSyntax method)
+ {
+ return semantic.GetSymbolInfo(method).Symbol ?? semantic.GetDeclaredSymbol(method);
+ }
+
+ internal static ClassDeclarationSyntax FindClass(Compilation compilationVal2, string ns, string expectedClassName)
+ {
+ foreach (var tree in compilationVal2.SyntaxTrees)
+ {
+ var rootSyntaxNode = tree.GetRootAsync().Result;
+
+ if (!GetNamespace(rootSyntaxNode).Contains(ns))
+ {
+ continue;
+ }
+
+ var classes = rootSyntaxNode.GetClasses(expectedClassName).ToList();
+
+ if (classes.Count == 1)
+ {
+ return classes[0];
+ }
+ }
+
+ return null;
+ }
+ }
+
+ internal static class Files
+ {
+ public static bool TryGetProtocolDirectory(out DirectoryInfo protocolDirectory)
+ {
+ bool found = false;
+
+ var currentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
+ protocolDirectory = Directory.GetParent(currentDirectory);
+ while (!found)
+ {
+ var temp = protocolDirectory.GetDirectories("Protocol", SearchOption.TopDirectoryOnly);
+ if (temp.Length == 1)
+ {
+ // Found protocol folder
+ protocolDirectory = temp[0];
+ found = true;
+ }
+ else
+ {
+ protocolDirectory = protocolDirectory.Parent;
+ }
+ }
+
+ return found;
+ }
+
+ public static (bool success, Stream stream) ReadTextFromFile(string pathToFile)
+ {
+ try
+ {
+ pathToFile = @"\\?\" + pathToFile;
+
+ var fileStream = new FileStream(pathToFile, FileMode.Open, FileAccess.Read, FileShare.Read);
+ return (true, fileStream);
+ }
+ catch (FileNotFoundException)
+ {
+ throw;
+ }
+ }
+ }
+
+ internal static class ProjectExtensions
+ {
+ private static Project AddDocuments(this Project project, IEnumerable files)
+ {
+ foreach (string file in files)
+ {
+ project = project.AddDocument(file, File.ReadAllText(file)).Project;
+ }
+ return project;
+ }
+
+ private static IEnumerable GetAllSourceFiles(string directoryPath)
+ {
+ var res = Directory.GetFiles(directoryPath, "*.cs", SearchOption.AllDirectories);
+
+ return res;
+ }
+
+ public static Project WithAllSourceFiles(this Project project)
+ {
+ string projectDirectory = Directory.GetParent(project.FilePath).FullName;
+ var files = GetAllSourceFiles(projectDirectory);
+ var newProject = project.AddDocuments(files);
+ return newProject;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ProtocolTests/CommonTests/TestAttributeTests.cs b/ProtocolTests/CommonTests/TestAttributeTests.cs
new file mode 100644
index 00000000..25d43b45
--- /dev/null
+++ b/ProtocolTests/CommonTests/TestAttributeTests.cs
@@ -0,0 +1,74 @@
+namespace SLDisValidatorUnitTests.CommonTests
+{
+ using System;
+ using FluentAssertions;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using SLDisValidator2.Common.Attributes;
+ using SLDisValidator2.Common.Enums;
+ using SLDisValidator2.Interfaces;
+ using SLDisValidator2.Tests.Protocol.CheckProtocolTag;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Model;
+ using CheckId = SLDisValidator2.Tests.Protocol.CheckProtocolTag.CheckId;
+
+
+ [TestClass]
+ public class TestAttributeTests
+ {
+ #region GetAttribute
+
+ [TestMethod]
+ public void GetAttribute_CheckProtocolTag_Valid()
+ {
+ IValidate test = new CheckProtocolTag();
+
+ TestAttribute attr = TestAttribute.GetAttribute(test);
+
+ attr.Should().BeAssignableTo(typeof(TestAttribute));
+ attr.Category.Should().BeEquivalentTo(Category.Protocol);
+ attr.CheckId.Should().Be(CheckId.CheckProtocolTag);
+ }
+
+ [TestMethod]
+ public void GetAttribute_CheckProtocolTag_ThrowsArgumentNullException()
+ {
+ Type type = null;
+ Assert.ThrowsException(() => TestAttribute.GetAttribute(type));
+ }
+
+ [TestMethod]
+ public void GetAttribute_RandomClass_Null()
+ {
+ TestAttribute attr = TestAttribute.GetAttribute(typeof(TestAttributeTests));
+
+ attr.Should().BeNull();
+ }
+
+ #endregion
+
+ #region Constructor
+
+ [TestMethod]
+ public void Constructor_Valid()
+ {
+ TestAttribute attr = new TestAttribute(5, Category.Protocol);
+
+ Assert.AreEqual((uint)5, attr.CheckId);
+ Assert.AreEqual(Category.Protocol, attr.Category);
+ Assert.AreEqual(TestOrder.Mid, attr.TestOrder);
+ }
+
+ [TestMethod]
+ public void Constructor_Valid2()
+ {
+ TestAttribute attr = new TestAttribute(5, Category.Param, TestOrder.Post1);
+
+ Assert.AreEqual((uint)5, attr.CheckId);
+ Assert.AreEqual(Category.Param, attr.Category);
+ Assert.AreEqual(TestOrder.Post1, attr.TestOrder);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/ProtocolTests/CommonTests/TestCollectorTests.cs b/ProtocolTests/CommonTests/TestCollectorTests.cs
new file mode 100644
index 00000000..31d3af20
--- /dev/null
+++ b/ProtocolTests/CommonTests/TestCollectorTests.cs
@@ -0,0 +1,62 @@
+namespace SLDisValidatorUnitTests.CommonTests
+{
+ using System;
+ using System.Collections.Generic;
+
+ using FluentAssertions;
+
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using Skyline.DataMiner.CICD.Validators.Common.Model;
+
+ using SLDisValidator2.Common;
+ using SLDisValidator2.Common.Attributes;
+ using SLDisValidator2.Interfaces;
+
+ [TestClass]
+ public class TestCollectorTests
+ {
+ [TestMethod]
+ public void GetAllValidateTests_Valid()
+ {
+ // Act
+ TestCollection