diff --git a/.gitignore b/.gitignore index 52371673..55b5d7fb 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ WinCC_OA_Test/Results/ WinCC_OA_Test/Projects/TfCustomizedQG/log/ WinCC_OA_Test/Projects/TfCustomizedQG/config/config *.dump +WinCC_OA_Test/CoverageReports/ \ No newline at end of file diff --git a/WinCCOA_QualityChecks/scripts/libs/classes/ErrorHdl/OaLogger.ctl b/WinCCOA_QualityChecks/scripts/libs/classes/ErrorHdl/OaLogger.ctl index cd59be44..c151e87b 100644 --- a/WinCCOA_QualityChecks/scripts/libs/classes/ErrorHdl/OaLogger.ctl +++ b/WinCCOA_QualityChecks/scripts/libs/classes/ErrorHdl/OaLogger.ctl @@ -7,7 +7,7 @@ // SPDX-License-Identifier: GPL-3.0-only // -#uses "stdVar" +#uses "std" class OaLogger { diff --git a/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgOverloadedFilesCheck/QgOverloadedFilesCheck.ctl b/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgOverloadedFilesCheck/QgOverloadedFilesCheck.ctl index 7c7158eb..dca2d8c2 100644 --- a/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgOverloadedFilesCheck/QgOverloadedFilesCheck.ctl +++ b/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgOverloadedFilesCheck/QgOverloadedFilesCheck.ctl @@ -11,6 +11,7 @@ #uses "classes/QualityGates/QgBase" #uses "classes/QualityGates/QgSettings" #uses "fileSys" +#uses "fileSystem" class QgOverloadedFilesCheck diff --git a/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgSettings.ctl b/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgSettings.ctl index 7e9810d8..026861ae 100644 --- a/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgSettings.ctl +++ b/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgSettings.ctl @@ -299,7 +299,7 @@ class QgSettings return makeDynString("FunctionData", "PanelCheck", "PanelFileShape", "PicturesFile", "InternalCheck", "OverloadedFilesCheck", - "SyntaxCheck", "ScriptData", + "SyntaxCheck", "QgCtrlCodeScriptData", "ScriptFile", "StaticCodeDir", "StaticDir"); } diff --git a/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgStaticCheck/CtrlCode/ScriptData.ctl b/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgStaticCheck/CtrlCode/QgCtrlCodeScriptData.ctl similarity index 92% rename from WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgStaticCheck/CtrlCode/ScriptData.ctl rename to WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgStaticCheck/CtrlCode/QgCtrlCodeScriptData.ctl index 7ee260ff..8c76bbfc 100644 --- a/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgStaticCheck/CtrlCode/ScriptData.ctl +++ b/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgStaticCheck/CtrlCode/QgCtrlCodeScriptData.ctl @@ -29,7 +29,7 @@ @note Call function calculate() before you want acces some file information. C-tor does not read the file to eliminate performacne. */ -class ScriptData +class QgCtrlCodeScriptData { //-------------------------------------------------------------------------------- //@public members @@ -43,7 +43,7 @@ class ScriptData Default c-tor @param filePath Full native path to file there shall be checked. */ - public ScriptData(const string filePath = "") + public QgCtrlCodeScriptData(const string filePath = "") { setPath(filePath); } @@ -166,7 +166,7 @@ class ScriptData */ public static int getMaxCountOfFunctions() { - shared_ptr settings = new QgSettings("ScriptData.script.countOfFunctions"); + shared_ptr settings = new QgSettings("QgCtrlCodeScriptData.script.countOfFunctions"); return (int)settings.getHighLimit(DEFAULT_FUNCCOUNT_HIGH); } @@ -177,7 +177,7 @@ class ScriptData */ public static int getMinCountOfFunctions() { - shared_ptr settings = new QgSettings("ScriptData.script.countOfFunctions"); + shared_ptr settings = new QgSettings("QgCtrlCodeScriptData.script.countOfFunctions"); return (int)settings.getLowLimit(DEFAULT_FUNCCOUNT_LOW); } @@ -189,7 +189,7 @@ class ScriptData */ public static int getMaxNLOC() { - shared_ptr settings = new QgSettings("ScriptData.script.NLOC"); + shared_ptr settings = new QgSettings("QgCtrlCodeScriptData.script.NLOC"); return (int)settings.getHighLimit(DEFAULT_NLOC_HIGH); } @@ -201,7 +201,7 @@ class ScriptData */ public static int getMinNLOC() { - shared_ptr settings = new QgSettings("ScriptData.script.NLOC"); + shared_ptr settings = new QgSettings("QgCtrlCodeScriptData.script.NLOC"); return (int)settings.getLowLimit(DEFAULT_NLOC_LOW); } @@ -213,7 +213,7 @@ class ScriptData */ public static float getMaxAvgCCN() { - shared_ptr settings = new QgSettings("ScriptData.script.avgCCN"); + shared_ptr settings = new QgSettings("QgCtrlCodeScriptData.script.avgCCN"); return (float)settings.getHighLimit(DEFAULT_AVGCCN_HIGH); } @@ -256,7 +256,7 @@ class ScriptData if (rc != 0) { - DebugFTN("ScriptData", __FUNCTION__, "!!! check if lizard is installed", rc, args, stdErr); + DebugFTN("QgCtrlCodeScriptData", __FUNCTION__, "!!! check if lizard is installed", rc, args, stdErr); return -2; } @@ -353,7 +353,7 @@ class ScriptData */ protected int validateIsCalucalted() { - shared_ptr settings = new QgSettings("ScriptData.script.isCalculated"); + shared_ptr settings = new QgSettings("QgCtrlCodeScriptData.script.isCalculated"); if (settings.isEnabled()) { @@ -389,7 +389,7 @@ class ScriptData */ protected validateCountOfFunctions() { - shared_ptr settings = new QgSettings("ScriptData.script.countOfFunctions"); + shared_ptr settings = new QgSettings("QgCtrlCodeScriptData.script.countOfFunctions"); if (settings.isEnabled()) { @@ -413,7 +413,7 @@ class ScriptData { if (getCountOfFunctions() > 1) // only when has more then 1 function { - shared_ptr settings = new QgSettings("ScriptData.script.avgCCN"); + shared_ptr settings = new QgSettings("QgCtrlCodeScriptData.script.avgCCN"); if (settings.isEnabled()) { @@ -435,7 +435,7 @@ class ScriptData */ protected validateNLOC() { - shared_ptr settings = new QgSettings("ScriptData.script.NLOC"); + shared_ptr settings = new QgSettings("QgCtrlCodeScriptData.script.NLOC"); if (settings.isEnabled()) { @@ -458,7 +458,7 @@ class ScriptData { if (getCountOfFunctions() > 1) // only when has more then 1 function { - shared_ptr settings = new QgSettings("ScriptData.script.avgNLOC"); + shared_ptr settings = new QgSettings("QgCtrlCodeScriptData.script.avgNLOC"); if (settings.isEnabled()) { diff --git a/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgStaticCheck/CtrlCode/ScriptFile.ctl b/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgStaticCheck/CtrlCode/ScriptFile.ctl index 8d4d6e3f..aa6d0556 100644 --- a/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgStaticCheck/CtrlCode/ScriptFile.ctl +++ b/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgStaticCheck/CtrlCode/ScriptFile.ctl @@ -8,7 +8,7 @@ // #uses "classes/QualityGates/QgBase" -#uses "classes/QualityGates/QgStaticCheck/CtrlCode/ScriptData" +#uses "classes/QualityGates/QgStaticCheck/CtrlCode/QgCtrlCodeScriptData" #uses "classes/FileSys/QgFile" #uses "classes/QualityGates/QgSettings" @@ -194,7 +194,7 @@ class ScriptFile : QgFile //------------------------------------------------------------------------------ public shared_ptr result; //!< Quality gate result - protected ScriptData _scriptData = ScriptData(); + protected QgCtrlCodeScriptData _scriptData = QgCtrlCodeScriptData(); static dyn_string _enabledExtensions = makeDynString(); string _extension; diff --git a/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgStaticCheck/Panels/PanelFile/PanelFileScript.ctl b/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgStaticCheck/Panels/PanelFile/PanelFileScript.ctl index 11a19fed..77b61a3b 100644 --- a/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgStaticCheck/Panels/PanelFile/PanelFileScript.ctl +++ b/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgStaticCheck/Panels/PanelFile/PanelFileScript.ctl @@ -7,9 +7,9 @@ // SPDX-License-Identifier: GPL-3.0-only // -#uses "classes/QualityGates/QgStaticCheck/CtrlCode/ScriptData" +#uses "classes/QualityGates/QgStaticCheck/CtrlCode/QgCtrlCodeScriptData" -class PanelFileScript : ScriptData +class PanelFileScript : QgCtrlCodeScriptData { public PanelFileScript(const string name = "") { @@ -71,7 +71,7 @@ class PanelFileScript : ScriptData fputs(getScript(), f); fclose(f); - if (ScriptData::calculate()) + if (QgCtrlCodeScriptData::calculate()) return -1; remove(_filePath); diff --git a/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgStaticCheck/StaticCodeDir.ctl b/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgStaticCheck/StaticCodeDir.ctl index a677a9d4..0c757e1e 100644 --- a/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgStaticCheck/StaticCodeDir.ctl +++ b/WinCCOA_QualityChecks/scripts/libs/classes/QualityGates/QgStaticCheck/StaticCodeDir.ctl @@ -11,7 +11,7 @@ #uses "classes/QualityGates/QgBase" #uses "classes/QualityGates/QgStaticCheck/StaticDir" #uses "classes/QualityGates/QgSettings" -#uses "stdVar" +#uses "std" class StaticCodeDir : StaticDir { diff --git a/WinCCOA_QualityChecks/scripts/libs/classes/Variables/Mapping.ctl b/WinCCOA_QualityChecks/scripts/libs/classes/Variables/Mapping.ctl index 6fd8f23f..26f101fb 100644 --- a/WinCCOA_QualityChecks/scripts/libs/classes/Variables/Mapping.ctl +++ b/WinCCOA_QualityChecks/scripts/libs/classes/Variables/Mapping.ctl @@ -9,7 +9,7 @@ //-------------------------------------------------------------------------------- // used libraries (#uses) -#uses "stdVar" +#uses "std" //-------------------------------------------------------------------------------- // declare variables and constans diff --git a/WinCCOA_QualityChecks/scripts/libs/fileSys.ctl b/WinCCOA_QualityChecks/scripts/libs/fileSys.ctl index f96b02f1..be21a860 100644 --- a/WinCCOA_QualityChecks/scripts/libs/fileSys.ctl +++ b/WinCCOA_QualityChecks/scripts/libs/fileSys.ctl @@ -3,68 +3,11 @@ // CtrlppCheck // a static code analysis tool for WinCC OA's Ctrl language // -// Copyright 2023 SIEMENS AG +// Copyright 2026 SIEMENS AG // // SPDX-License-Identifier: GPL-3.0-only // -//--------------------------------------------------------------------------------------------------------------------------------------- -/** - @brief Function returns list with all files recursive, relative to dir. - @details Files are absolute and native pathes. - - @param dir Directory path - @param pattern Pattern for filter. See also getFileNames() - @param filter Filter. See also getFileNames() - @return List with paths to filtered files. -*/ -dyn_string getFileNamesRecursive(string dir, string pattern = "*", int filter = FILTER_FILES) -{ - if ((dir == "") || !isdir(dir)) - return makeDynString(); - - dyn_string dirs = getFileNames(dir, "*", FILTER_DIRS); - dyn_string filtered = getFileNames(dir, pattern, filter); - dyn_string result; - - int len = dynlen(filtered); - - for (int i = 1; i <= len; i++) - { - string subDir = filtered[i]; - - if (!_isValidDirName(subDir)) - continue; - - dynAppend(result, makeNativePath(dir + "/" + subDir)); - } - - len = dynlen(dirs); - - for (int i = 1; i <= len; i++) - { - string subDir = dirs[i]; - - if (!_isValidDirName(subDir)) - continue; - - dynAppend(result, getFileNamesRecursive(makeNativePath(dir + "/" + subDir), pattern, filter)); - } - - string delims = makeNativePath("//"); - string delim = makeNativePath("/"); - - for (int i = 1; i <= dynlen(result); i++) - { - strreplace(result[i], delims, delim); - } - - dynUnique(result); - dynSort(result); - - return result; -} - //--------------------------------------------------------------------------------------------------------------------------------------- dyn_string getSubProjPathes() { @@ -77,12 +20,3 @@ dyn_string getSubProjPathes() return pathes; } - -//--------------------------------------------------------------------------------------------------------------------------------------- -private bool _isValidDirName(const string name) -{ - if ((name == "..") || (name == "") || (name == ".")) - return FALSE; - - return TRUE; -} diff --git a/WinCCOA_QualityChecks/scripts/libs/scriptEditor/ctrlPPCheck_ext.ctl b/WinCCOA_QualityChecks/scripts/libs/scriptEditor/ctrlPPCheck_ext.ctl index ad6000ba..abe32afc 100644 --- a/WinCCOA_QualityChecks/scripts/libs/scriptEditor/ctrlPPCheck_ext.ctl +++ b/WinCCOA_QualityChecks/scripts/libs/scriptEditor/ctrlPPCheck_ext.ctl @@ -20,7 +20,7 @@ //--------------------------------------------------------------------------------------------------------------------------------------- #uses "classes/QualityGates/Qg" -#uses "classes/QualityGates/QgStaticCheck/CtrlCode/ScriptData" +#uses "classes/QualityGates/QgStaticCheck/CtrlCode/QgCtrlCodeScriptData" #uses "classes/QualityGates/Tools/CppCheck/CppCheck" #uses "panel" @@ -100,7 +100,7 @@ void ctrlPPCheck() { // in new scope to eliminate memory usage - ScriptData script; + QgCtrlCodeScriptData script; script.setPath(path); script.calculate(); // script.validate(); diff --git a/WinCC_OA_Test/Projects/TfCustomizedQG/scripts/astyle.ctl b/WinCC_OA_Test/Projects/TfCustomizedQG/scripts/astyle.ctl index 4ed43ec9..7a415227 100644 --- a/WinCC_OA_Test/Projects/TfCustomizedQG/scripts/astyle.ctl +++ b/WinCC_OA_Test/Projects/TfCustomizedQG/scripts/astyle.ctl @@ -10,6 +10,8 @@ //-------------------------------------------------------------------------------- // Libraries used (#uses) +#uses "fileSystem" + //-------------------------------------------------------------------------------- // Variables and Constants diff --git a/WinCC_OA_Test/Projects/TfCustomizedQG/scripts/copyright.ctl b/WinCC_OA_Test/Projects/TfCustomizedQG/scripts/copyright.ctl index 79b54e92..6e7bd36e 100644 --- a/WinCC_OA_Test/Projects/TfCustomizedQG/scripts/copyright.ctl +++ b/WinCC_OA_Test/Projects/TfCustomizedQG/scripts/copyright.ctl @@ -14,6 +14,8 @@ //-------------------------------------------------------------------------------- // Variables and Constants +#uses "fileSystem" + //-------------------------------------------------------------------------------- /** */ diff --git a/WinCC_OA_Test/Projects/TfCustomizedQG/scripts/libs/classes/testFramework/testProject/HookTfTestProject.ctl b/WinCC_OA_Test/Projects/TfCustomizedQG/scripts/libs/classes/testFramework/testProject/HookTfTestProject.ctl index 68a50c6b..e00e0e79 100644 --- a/WinCC_OA_Test/Projects/TfCustomizedQG/scripts/libs/classes/testFramework/testProject/HookTfTestProject.ctl +++ b/WinCC_OA_Test/Projects/TfCustomizedQG/scripts/libs/classes/testFramework/testProject/HookTfTestProject.ctl @@ -26,4 +26,71 @@ class HookTfTestProject : TfTestProject dynAppend(list, "WinCCOA_QualityChecks"); return list; } + + + protected ProjEnvComponent gedi = new ProjEnvComponent(UI_COMPONENT); + + //------------------------------------------------------------------------------ + /** Checks if the IDE is enabled or not. + */ + private bool useIDE() + { + string startIDE = getenv("startIDE"); + + if (!this.isLocalProject || (startIDE == "") || (startIDE != "true")) + { + return false; + } + + return true; + } + + //------------------------------------------------------------------------------ + /** Start Gedi (WinCC OA IDE) in case of startIDE is enabled. + */ + protected int _afterStarted() + { + int rc = TfTestProject::_afterStarted(); + + if (rc) + return rc; + + if (useIDE()) + { + dyn_string arguments = makeDynString("-m", "gedi"); + + if (!this.isProjWithDb(_packageSelection)) + { + // project without DB start Gedi with -n (no event / db connection) + dynAppend(arguments, "-n"); + } + + gedi.setProj(this.getId()); + gedi.setOptions(arguments); + gedi.setAsync(TRUE); + gedi.setDetached(TRUE); + gedi.start(); + } + + return 0; + } + + //------------------------------------------------------------------------------ + /** Start pause panel in case of startIDE is enabled. + * @note Tests are executed the panel is closed. + */ + protected int _beforeStartTestManagers() + { + if (useIDE()) + { + ProjEnvComponent pausePanel = new ProjEnvComponent(UI_COMPONENT); + + pausePanel.setOptions(makeDynString("-p", "OaTest/pause.pnl", "-n")); + pausePanel.setProj(this.getId()); + pausePanel.start(); + gedi.stop(); + } + + return TfTestProject::_beforeStartTestManagers(); + } }; diff --git a/WinCC_OA_Test/Templates/newUnitTest.ctl b/WinCC_OA_Test/Templates/newUnitTest.ctl new file mode 100644 index 00000000..55429aff --- /dev/null +++ b/WinCC_OA_Test/Templates/newUnitTest.ctl @@ -0,0 +1,41 @@ +// $License: NOLICENSE +/** Tests for the library: scripts/libs/$origLibRelPath. + + @file $relPath + @test Unit tests for the library: scripts/libs/$origLibRelPath + @copyright $copyright + @author $author + */ + +//----------------------------------------------------------------------------- +// Libraries used (#uses) +#uses "$origLibRelPathWithoutExtension" // tested object +#uses "classes/oaTest/OaTest" // oaTest basic class + +//----------------------------------------------------------------------------- +// Variables and Constants + +//----------------------------------------------------------------------------- +/** Tests for $origLibName.ctl +*/ +class newTestTemplate : OaTest +{ + //--------------------------------------------------------------------------- + /** + @test Describe the test scenario here. + */ + public int testSetAssertionState() + { + // type your test script here like + this.assertEqual("currentValue1", "currentValue1"); + + return 0; + } +}; + +//----------------------------------------------------------------------------- +void main() +{ + newTestTemplate test; + test.startAll(); +} diff --git a/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/createUnitTests-promt.md b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/createUnitTests-promt.md new file mode 100644 index 00000000..4dfc139b --- /dev/null +++ b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/createUnitTests-promt.md @@ -0,0 +1,168 @@ +# Prompt: Create/Update WinCC OA Unit Tests (oaUnit) + +Use this as a short **copy/paste prompt** for an AI coding agent. + +## You provide (inputs) + +- Target Ctrl file (repo-relative): `classes/.../*.ctl` +- WinCC OA version (default `3.20`) +- What should be verified (2–6 bullet points) + +## Hard repo rules (don't violate) + +- Unit tests live under: + `WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/` +- Mirror the production path under `scripts/tests/libs/`: + - Tested: `classes/Variables/Float.ctl` + - Test: `WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/Variables/Float.ctl` +- Tests must `#uses "classes/oaTest/OaTest"` and call `test.startAll()` from `main()`. +- Add/update registration in: + `WinCC_OA_Test/TestSuites/suite_Common/testProj.unit.config` (`TEST_MANAGERS` entry). +- The unit test project runs **without DB** (`PACK_SEL: 2`) → no `dp*()` usage. +- Ctrl limitation: avoid nested class definitions (define mocks at top-level). + +## Known dependency constraints (no-DB incompatible) + +Some libraries cannot be tested in the no-DB (`PACK_SEL: 2`) environment: + +| Dependency | Impact | Examples | +|------------|--------|----------| +| `#uses "CtrlPv2Admin"` | ❌ Fails at load time | FunctionData.ctl, ToolLizard.ctl | +| `#uses "panel"` | ❌ Fails at load time | CppCheck.ctl | +| `#uses "CtrlXml"` | ⚠️ May fail | XML processing classes | +| `dp*()` functions | ❌ Runtime error | Any DB-dependent code | +| `getCatStr()` | ⚠️ Returns key | Message catalog lookups | + +**Workaround:** If a tested class has these dependencies via `#uses`, the test cannot run in `PACK_SEL: 2` mode. Mark such files in `toDoTests.md` with "⚠️ deps" note. + +## Ctrl language quirks + +- **Operator overloading:** Don't use `obj1 + obj2` syntax. Call the method explicitly: `obj1.opPlus(obj2)`. +- **Enums from other files:** If an enum is defined in a file with incompatible dependencies, copy it locally in the test file. +- **`main()` function:** Must be declared as `void main()` or just `main()`, and must call `exit(0)` at the end. + +## Interactive development workflow + +For faster test iteration, use the `-startIDE` option: + +```powershell +# 1. Start project with GEDI (runs once, stays open) +.\executeTests.cmd -oaVersion 3.20 -oaTestRunId Common -startIDE + +# 2. Run individual tests directly (fast iteration) +& "C:\Siemens\Automation\WinCC_OA\3.20\bin\WCCOActrl.exe" -proj Common_Unit_3.20 -n tests/libs/classes/Variables/Mapping.ctl +``` + +The `-n` flag is required because the project has no database (`PACK_SEL: 2`). + +## Copy/paste prompt + +```text +You are a coding agent in a VS Code workspace. + +Create or update an oaUnit unit test for: +- Target Ctrl file: +- WinCC OA version: 3.20 + +Required outputs: +1) Create/update the test file at: + WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/ +2) Ensure WinCC_OA_Test/TestSuites/suite_Common/testProj.unit.config has a TEST_MANAGERS entry: + "tests/libs/ -n" + +Constraints: +- No DB access (PACK_SEL: 2), so no dp*(). +- Keep tests deterministic. +- If the tested code can terminate/abort a manager (fatal/throwError/etc.), use a mock/stub seam to keep the test manager alive. +- Don't use operator syntax like `obj1 + obj2`, call methods explicitly: `obj1.opPlus(obj2)`. + +Preferred style (use this): +- Implement getAllTestCaseIds() + startTestCase(tcId) with a switch. + +Reference examples for style only: +- WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/Variables/Float.ctl +- WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/Variables/Mapping.ctl +- WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/Tools/CppCheck/CppCheckSettings.ctl + +What to test: + +``` + +## Minimal skeleton (preferred repo style) + +```ctrl +//-------------------------------------------------------------------------------- +/** + @file $relPath + @copyright Copyright 2026 SIEMENS AG + SPDX-License-Identifier: GPL-3.0-only +*/ + +/*! + * @brief Tests for class: + */ + +//-------------------------------------------------------------------------------- +// used libraries (#uses) +#uses "" /*!< tested object */ +#uses "classes/oaTest/OaTest" + +//-------------------------------------------------------------------------------- +class Tst : OaTest +{ + public dyn_string getAllTestCaseIds() + { + return makeDynString( + "_testCase1", + "_testCase2" + ); + } + + protected int startTestCase(const string tcId) + { + switch (tcId) + { + case "_testCase1": + { + // Arrange + // Act + // Assert (assertTrue/assertFalse/assertEqual/assertNotEqual) + return 0; + } + + case "_testCase2": + { + // ... + return 0; + } + } + + return -1; + } +}; + +//-------------------------------------------------------------------------------- +main() +{ + Tst test; + test.startAll(); + exit(0); +} +``` + +## Available assertion methods (from OaTest) + +| Method | Description | +|--------|-------------| +| `assertTrue(bool)` | Assert value is TRUE | +| `assertFalse(bool)` | Assert value is FALSE | +| `assertEqual(a, b)` | Assert a == b | +| `assertNotEqual(a, b)` | Assert a != b | + +## Checklist before committing + +- [ ] Test file created at correct path +- [ ] `testProj.unit.config` updated with TEST_MANAGERS entry +- [ ] Test runs successfully: `WCCOActrl.exe -proj Common_Unit_3.20 -n ` +- [ ] No `dp*()` calls or DB-dependent code +- [ ] Exit code is 0 (all tests pass) diff --git a/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/ErrorHdl/OaLogger.ctl b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/ErrorHdl/OaLogger.ctl new file mode 100644 index 00000000..b53462f0 --- /dev/null +++ b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/ErrorHdl/OaLogger.ctl @@ -0,0 +1,240 @@ +//-------------------------------------------------------------------------------- +/** + @file $relPath + @copyright Copyright 2023 SIEMENS AG + SPDX-License-Identifier: GPL-3.0-only +*/ + +/*! + * @brief Tests for class: OaLogger + */ + +//-------------------------------------------------------------------------------- +// used libraries (#uses) +#uses "std" +#uses "classes/ErrorHdl/OaLogger" /*!< tested object */ +#uses "classes/oaTest/OaTest" + +//-------------------------------------------------------------------------------- +class MockOaLogger : OaLogger +{ + public bool hasFatalBeenCalled = FALSE; + public int callCount = 0; + public int lastOriginalPrio = 0; + public int lastEffectivePrio = 0; + public anytype lastCodeOrError; + public anytype lastNote; + public anytype lastNote2; + public anytype lastNote3; + + protected _throw(const anytype &codeOrError, + const int prio, + const anytype ¬e, + const anytype ¬e2, + const anytype ¬e3) + { + int prioToUse = prio; + callCount++; + + lastOriginalPrio = prio; + lastCodeOrError = codeOrError; + lastNote = note; + lastNote2 = note2; + lastNote3 = note3; + + this.hasFatalBeenCalled = prio == PRIO_FATAL; + if (this.hasFatalBeenCalled) + { + prioToUse = PRIO_SEVERE; // avoid killing the manager during tests + } + + lastEffectivePrio = prioToUse; + + // Keep the unit test manager alive: + // - OaLogger uses throwError() for PRIO_INFO/PRIO_WARNING which can lead to aborts + // in this test environment. + // - For PRIO_SEVERE (and downgraded PRIO_FATAL) we still forward to validate the + // throwing behavior. + if (prioToUse == PRIO_INFO || prioToUse == PRIO_WARNING) + return; + + OaLogger::_throw(codeOrError, prioToUse, note, note2, note3); + } +}; + +//-------------------------------------------------------------------------------- +class TstOaLogger : OaTest +{ + public dyn_string getAllTestCaseIds() + { + return makeDynString("OaLogger"); + } + + protected int startTestCase(const string tcId) + { + switch (tcId) + { + case "OaLogger": + { + MockOaLogger logger; + + bool thrown; + + // info() should not abort the test manager (mock suppresses throwError()) + thrown = FALSE; + try { logger.info("info message"); } catch { thrown = TRUE; } + assertFalse(thrown); + assertFalse(logger.hasFatalBeenCalled); + assertTrue(logger.lastOriginalPrio == PRIO_INFO); + assertTrue(logger.lastEffectivePrio == PRIO_INFO); + assertTrue(isA(logger.lastCodeOrError, STRING_VAR)); + assertTrue((string)logger.lastCodeOrError == "info message"); + assertTrue(isNull(logger.lastNote)); + assertTrue(isNull(logger.lastNote2)); + assertTrue(isNull(logger.lastNote3)); + + thrown = FALSE; + try { logger.info(1); } catch { thrown = TRUE; } + assertFalse(thrown); + assertFalse(logger.hasFatalBeenCalled); + assertTrue(logger.lastOriginalPrio == PRIO_INFO); + assertTrue(logger.lastEffectivePrio == PRIO_INFO); + assertTrue((int)logger.lastCodeOrError == 1); + assertTrue(isNull(logger.lastNote)); + + thrown = FALSE; + try { logger.info(2, "note"); } catch { thrown = TRUE; } + assertFalse(thrown); + assertFalse(logger.hasFatalBeenCalled); + assertTrue(logger.lastOriginalPrio == PRIO_INFO); + assertTrue(logger.lastEffectivePrio == PRIO_INFO); + assertTrue((int)logger.lastCodeOrError == 2); + assertTrue(isA(logger.lastNote, STRING_VAR)); + assertTrue((string)logger.lastNote == "note"); + assertTrue(isNull(logger.lastNote2)); + assertTrue(isNull(logger.lastNote3)); + + thrown = FALSE; + try { logger.info(3, "note", "note2"); } catch { thrown = TRUE; } + assertFalse(thrown); + assertFalse(logger.hasFatalBeenCalled); + assertTrue(logger.lastOriginalPrio == PRIO_INFO); + assertTrue(logger.lastEffectivePrio == PRIO_INFO); + assertTrue(isA(logger.lastNote2, STRING_VAR)); + assertTrue((string)logger.lastNote2 == "note2"); + + thrown = FALSE; + try { logger.info(4, "note", "note2", "note3"); } catch { thrown = TRUE; } + assertFalse(thrown); + assertFalse(logger.hasFatalBeenCalled); + assertTrue(logger.lastOriginalPrio == PRIO_INFO); + assertTrue(logger.lastEffectivePrio == PRIO_INFO); + assertTrue(isA(logger.lastNote3, STRING_VAR)); + assertTrue((string)logger.lastNote3 == "note3"); + + // warning() should not abort the test manager (mock suppresses throwError()) + thrown = FALSE; + try { logger.warning("warning message"); } catch { thrown = TRUE; } + assertFalse(thrown); + assertFalse(logger.hasFatalBeenCalled); + assertTrue(logger.lastOriginalPrio == PRIO_WARNING); + assertTrue(logger.lastEffectivePrio == PRIO_WARNING); + assertTrue(isA(logger.lastCodeOrError, STRING_VAR)); + assertTrue((string)logger.lastCodeOrError == "warning message"); + + thrown = FALSE; + try { logger.warning(10); } catch { thrown = TRUE; } + assertFalse(thrown); + assertFalse(logger.hasFatalBeenCalled); + assertTrue(logger.lastOriginalPrio == PRIO_WARNING); + assertTrue(logger.lastEffectivePrio == PRIO_WARNING); + assertTrue((int)logger.lastCodeOrError == 10); + + thrown = FALSE; + try { logger.warning(11, "note"); } catch { thrown = TRUE; } + assertFalse(thrown); + assertFalse(logger.hasFatalBeenCalled); + assertTrue(logger.lastOriginalPrio == PRIO_WARNING); + assertTrue(logger.lastEffectivePrio == PRIO_WARNING); + assertTrue((int)logger.lastCodeOrError == 11); + assertTrue(isA(logger.lastNote, STRING_VAR)); + assertTrue((string)logger.lastNote == "note"); + + thrown = FALSE; + try { logger.warning(12, "note", "note2"); } catch { thrown = TRUE; } + assertFalse(thrown); + assertFalse(logger.hasFatalBeenCalled); + assertTrue(logger.lastOriginalPrio == PRIO_WARNING); + assertTrue(logger.lastEffectivePrio == PRIO_WARNING); + assertTrue((int)logger.lastCodeOrError == 12); + assertTrue(isA(logger.lastNote2, STRING_VAR)); + assertTrue((string)logger.lastNote2 == "note2"); + + thrown = FALSE; + try { logger.warning(13, "note", "note2", "note3"); } catch { thrown = TRUE; } + assertFalse(thrown); + assertFalse(logger.hasFatalBeenCalled); + assertTrue(logger.lastOriginalPrio == PRIO_WARNING); + assertTrue(logger.lastEffectivePrio == PRIO_WARNING); + assertTrue((int)logger.lastCodeOrError == 13); + assertTrue(isA(logger.lastNote3, STRING_VAR)); + assertTrue((string)logger.lastNote3 == "note3"); + + // fatal() must be mocked: PRIO_FATAL kills the manager. + // In the mock it is downgraded to PRIO_SEVERE, therefore it must throw. + thrown = FALSE; + try + { + logger.fatal("fatal message"); + } + catch + { + thrown = TRUE; + } + assertTrue(thrown); + assertTrue(logger.hasFatalBeenCalled); + assertTrue(logger.lastOriginalPrio == PRIO_FATAL); + assertTrue(logger.lastEffectivePrio == PRIO_SEVERE); + assertTrue(isA(logger.lastCodeOrError, STRING_VAR)); + assertTrue((string)logger.lastCodeOrError == "fatal message"); + + // severe() MUST throw + thrown = FALSE; + try + { + logger.severe("severe message"); + } + catch + { + thrown = TRUE; + } + assertTrue(thrown); + + // severe() with custom msg catalog must still throw + thrown = FALSE; + try + { + OaLogger catLogger = OaLogger("UnitTest"); + catLogger.severe(99, "note"); + } + catch + { + thrown = TRUE; + } + assertTrue(thrown); + + return 0; + } + } + + return -1; + } +}; + +//-------------------------------------------------------------------------------- +main() +{ + TstOaLogger test; + test.startAll(); + exit(0); +} diff --git a/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/FileSys/QgFile.ctl b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/FileSys/QgFile.ctl new file mode 100644 index 00000000..d95e686e --- /dev/null +++ b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/FileSys/QgFile.ctl @@ -0,0 +1,156 @@ +//-------------------------------------------------------------------------------- +/** + @file $relPath + @copyright Copyright 2026 SIEMENS AG + SPDX-License-Identifier: GPL-3.0-only +*/ + +/*! + * @brief Tests for class: QgFile + */ + +//-------------------------------------------------------------------------------- +// used libraries (#uses) +#uses "classes/FileSys/QgFile" /*!< tested object */ +#uses "classes/oaTest/OaTest" + +//-------------------------------------------------------------------------------- +class TstQgFile : OaTest +{ + public dyn_string getAllTestCaseIds() + { + return makeDynString( + "QgFile_ctor_empty", + "QgFile_ctor_with_path", + "QgFile_setFilePath_getFilePath", + "QgFile_getName", + "QgFile_mk_exists_rm", + "QgFile_isExample", + "QgFile_isTest", + "QgFile_isPatternMatch" + ); + } + + protected int startTestCase(const string tcId) + { + switch (tcId) + { + case "QgFile_ctor_empty": + { + QgFile f; + assertEqual(f.getFilePath(), ""); + assertEqual(f.getName(), ""); + return 0; + } + + case "QgFile_ctor_with_path": + { + QgFile f = QgFile("/path/to/myfile.ctl"); + assertEqual(f.getName(), "myfile.ctl"); + return 0; + } + + case "QgFile_setFilePath_getFilePath": + { + QgFile f; + f.setFilePath("/some/path/file.txt"); + assertTrue(f.getFilePath() != ""); + assertEqual(f.getName(), "file.txt"); + + f.setFilePath("/another/path.xml"); + assertEqual(f.getName(), "path.xml"); + return 0; + } + + case "QgFile_getName": + { + QgFile f1 = QgFile("/long/path/to/script.ctl"); + assertEqual(f1.getName(), "script.ctl"); + + QgFile f2 = QgFile("simple.txt"); + assertEqual(f2.getName(), "simple.txt"); + + QgFile f3 = QgFile("/path/with.dots/file.name.ext"); + assertEqual(f3.getName(), "file.name.ext"); + return 0; + } + + case "QgFile_mk_exists_rm": + { + // Create a temp file path + string tempPath = PROJ_PATH + "data/" + createUuid() + ".tmp"; + QgFile f = QgFile(tempPath); + + // Initially should not exist + assertFalse(f.exists()); + + // Create the file + int mkResult = f.mk(); + assertEqual(mkResult, 0); + assertTrue(f.exists()); + + // Creating again should succeed (already exists) + mkResult = f.mk(); + assertEqual(mkResult, 0); + + // Remove the file + int rmResult = f.rm(); + assertEqual(rmResult, 0); + assertFalse(f.exists()); + + // Removing again should succeed (already gone) + rmResult = f.rm(); + assertEqual(rmResult, 0); + return 0; + } + + case "QgFile_isExample": + { + QgFile f1 = QgFile("/proj/scripts/examples/test.ctl"); + assertTrue(f1.isExample()); + + QgFile f2 = QgFile("/proj/scripts/libs/mylib.ctl"); + assertFalse(f2.isExample()); + + QgFile f3 = QgFile("/proj/panels/examples/panel.pnl"); + assertTrue(f3.isExample()); + return 0; + } + + case "QgFile_isTest": + { + QgFile f1 = QgFile("/proj/scripts/tests/mytest.ctl"); + assertTrue(f1.isTest()); + + QgFile f2 = QgFile("/proj/scripts/libs/mylib.ctl"); + assertFalse(f2.isTest()); + + QgFile f3 = QgFile("/suite/sub_unit/scripts/tests/libs/test.ctl"); + assertTrue(f3.isTest()); + return 0; + } + + case "QgFile_isPatternMatch": + { + QgFile f = QgFile("/path/to/script.ctl"); + + assertTrue(f.isPatternMatch("*.ctl")); + assertTrue(f.isPatternMatch("*script*")); + assertTrue(f.isPatternMatch("*/to/*")); + assertFalse(f.isPatternMatch("*.xml")); + assertFalse(f.isPatternMatch("*panel*")); + return 0; + } + } + + return -1; + } +}; + +//-------------------------------------------------------------------------------- +main() +{ + TstQgFile test; + test.startAll(); + exit(0); +} diff --git a/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/AddOn/FileSys/QgAddOnResultsDir.ctl b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/AddOn/FileSys/QgAddOnResultsDir.ctl new file mode 100644 index 00000000..1a068334 --- /dev/null +++ b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/AddOn/FileSys/QgAddOnResultsDir.ctl @@ -0,0 +1,94 @@ +//-------------------------------------------------------------------------------- +/** + @file $relPath + @copyright Copyright 2026 SIEMENS AG + SPDX-License-Identifier: GPL-3.0-only +*/ + +/*! + * @brief Tests for class: QgAddOnResultsDir + */ + +//-------------------------------------------------------------------------------- +// used libraries (#uses) +#uses "classes/QualityGates/AddOn/FileSys/QgAddOnResultsDir" /*!< tested object */ +#uses "classes/oaTest/OaTest" + +//-------------------------------------------------------------------------------- +class TstQgAddOnResultsDir : OaTest +{ + public dyn_string getAllTestCaseIds() + { + return makeDynString( + "QgAddOnResultsDir_ctor", + "QgAddOnResultsDir_setQgId", + "QgAddOnResultsDir_getDirPath_format", + "QgAddOnResultsDir_getRunningQgs_is_dyn_string", + "QgAddOnResultsDir_getHistoryDirs_is_dyn_string" + ); + } + + protected int startTestCase(const string tcId) + { + switch (tcId) + { + case "QgAddOnResultsDir_ctor": + { + QgAddOnResultsDir resDir; + // Constructor should work + assertTrue(TRUE); + return 0; + } + + case "QgAddOnResultsDir_setQgId": + { + QgAddOnResultsDir resDir; + resDir.setQgId("TestQG"); + // setQgId should not throw + assertTrue(TRUE); + return 0; + } + + case "QgAddOnResultsDir_getDirPath_format": + { + QgAddOnResultsDir resDir; + resDir.setQgId("TestQG"); + string path = resDir.getDirPath(); + // Path should contain TestQG + assertTrue(strpos(path, "TestQG") >= 0); + // Path should contain QualityGates + assertTrue(strpos(path, "QualityGates") >= 0); + return 0; + } + + case "QgAddOnResultsDir_getRunningQgs_is_dyn_string": + { + dyn_string qgs = QgAddOnResultsDir::getRunningQgs(); + // Should return a dyn_string (possibly empty) + assertTrue(getType(qgs) == DYN_STRING_VAR); + return 0; + } + + case "QgAddOnResultsDir_getHistoryDirs_is_dyn_string": + { + QgAddOnResultsDir resDir; + resDir.setQgId("NonExistentQG"); + dyn_string histDirs = resDir.getHistoryDirs(); + // Should return empty dyn_string for non-existent QG + assertTrue(getType(histDirs) == DYN_STRING_VAR); + assertEqual(dynlen(histDirs), 0); + return 0; + } + } + + return -1; + } +}; + +//-------------------------------------------------------------------------------- +main() +{ + TstQgAddOnResultsDir test; + test.startAll(); + exit(0); +} diff --git a/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/AddOn/Output/QgAddOnResult.ctl b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/AddOn/Output/QgAddOnResult.ctl new file mode 100644 index 00000000..7b67445f --- /dev/null +++ b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/AddOn/Output/QgAddOnResult.ctl @@ -0,0 +1,141 @@ +//-------------------------------------------------------------------------------- +/** + @file $relPath + @copyright Copyright 2026 SIEMENS AG + SPDX-License-Identifier: GPL-3.0-only +*/ + +/*! + * @brief Tests for class: QgAddOnResult + */ + +//-------------------------------------------------------------------------------- +// used libraries (#uses) +#uses "classes/QualityGates/AddOn/Output/QgAddOnResult" /*!< tested object */ +#uses "classes/oaTest/OaTest" + +//-------------------------------------------------------------------------------- +class TstQgAddOnResult : OaTest +{ + public dyn_string getAllTestCaseIds() + { + return makeDynString( + "QgAddOnResult_constants", + "QgAddOnResult_ctor_default", + "QgAddOnResult_setState_success", + "QgAddOnResult_setState_warning", + "QgAddOnResult_setState_error", + "QgAddOnResult_stateToString_success", + "QgAddOnResult_stateToString_warning", + "QgAddOnResult_stateToString_error", + "QgAddOnResult_setData", + "QgAddOnResultState_enum_values" + ); + } + + protected int startTestCase(const string tcId) + { + switch (tcId) + { + case "QgAddOnResult_constants": + { + // Test class constants + assertEqual(QgAddOnResult::MIN_VALID_SCORE, 1.0); + assertEqual(QgAddOnResult::NOT_VALID_SCORE, 0.0); + assertEqual(QgAddOnResult::KEY_SCORE_REASON, "Reason"); + assertEqual(QgAddOnResult::KEY_SCORE_PERCENT, "%"); + assertEqual(QgAddOnResult::KEY_SCORE_TOTAL_POINTS, "Total points"); + assertEqual(QgAddOnResult::KEY_SCORE_ERROR_POINTS, "Error points"); + assertEqual(QgAddOnResult::KEY_QG_RESULT_TESTVERSION, "qgTestVersionResults"); + assertEqual(QgAddOnResult::KEY_QG_RESULT_SUM, "qgSummary"); + assertEqual(QgAddOnResult::KEY_QG_RESULT_SCORE, "score"); + return 0; + } + + case "QgAddOnResult_ctor_default": + { + QgAddOnResult result; + // Constructor should work without error + assertTrue(TRUE); + return 0; + } + + case "QgAddOnResult_setState_success": + { + QgAddOnResult result; + result.setState(QgAddOnResultState::success); + assertEqual(result.stateToString(), "success"); + return 0; + } + + case "QgAddOnResult_setState_warning": + { + QgAddOnResult result; + result.setState(QgAddOnResultState::warning); + assertEqual(result.stateToString(), "warning"); + return 0; + } + + case "QgAddOnResult_setState_error": + { + QgAddOnResult result; + result.setState(QgAddOnResultState::error); + assertEqual(result.stateToString(), "error"); + return 0; + } + + case "QgAddOnResult_stateToString_success": + { + QgAddOnResult result; + result.setState(QgAddOnResultState::success); + assertEqual(result.stateToString(), "success"); + return 0; + } + + case "QgAddOnResult_stateToString_warning": + { + QgAddOnResult result; + result.setState(QgAddOnResultState::warning); + assertEqual(result.stateToString(), "warning"); + return 0; + } + + case "QgAddOnResult_stateToString_error": + { + QgAddOnResult result; + result.setState(QgAddOnResultState::error); + assertEqual(result.stateToString(), "error"); + return 0; + } + + case "QgAddOnResult_setData": + { + QgAddOnResult result; + mapping data = makeMapping("key1", "value1", "key2", 42); + result.setData(data); + // setData should complete without error + assertTrue(TRUE); + return 0; + } + + case "QgAddOnResultState_enum_values": + { + // Test enum values exist and are distinct + assertTrue((int)QgAddOnResultState::success != (int)QgAddOnResultState::warning); + assertTrue((int)QgAddOnResultState::warning != (int)QgAddOnResultState::error); + assertTrue((int)QgAddOnResultState::error != (int)QgAddOnResultState::failed); + return 0; + } + } + + return -1; + } +}; + +//-------------------------------------------------------------------------------- +main() +{ + TstQgAddOnResult test; + test.startAll(); + exit(0); +} diff --git a/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/QgAddOnResultErr.ctl b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/QgAddOnResultErr.ctl new file mode 100644 index 00000000..5b56fd07 --- /dev/null +++ b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/QgAddOnResultErr.ctl @@ -0,0 +1,93 @@ +//-------------------------------------------------------------------------------- +/** + @file $relPath + @copyright Copyright 2026 SIEMENS AG + SPDX-License-Identifier: GPL-3.0-only +*/ + +/*! + * @brief Tests for class: QgAddOnResultErr + */ + +//-------------------------------------------------------------------------------- +// used libraries (#uses) +#uses "classes/QualityGates/QgAddOnResultErr" /*!< tested object */ +#uses "classes/oaTest/OaTest" + +//-------------------------------------------------------------------------------- +class TstQgAddOnResultErr : OaTest +{ + public dyn_string getAllTestCaseIds() + { + return makeDynString( + "QgAddOnResultErr_ctor", + "QgAddOnResultErr_getPriority", + "QgAddOnResultErr_getPriorityAsText", + "QgAddOnResultErr_toMap" + ); + } + + protected int startTestCase(const string tcId) + { + switch (tcId) + { + case "QgAddOnResultErr_ctor": + { + // Create error with different error codes + QgAddOnResultErr errUnknown = QgAddOnResultErr(PRIO_WARNING, QgAddOnResultErrCode::Unknown, "TestNode"); + assertNotEqual(errUnknown.getText(), ""); + + QgAddOnResultErr errDoesNotRun = QgAddOnResultErr(PRIO_SEVERE, QgAddOnResultErrCode::DoesNotRunSuccessfull, "TestScript.ctl"); + assertNotEqual(errDoesNotRun.getText(), ""); + return 0; + } + + case "QgAddOnResultErr_getPriority": + { + QgAddOnResultErr errInfo = QgAddOnResultErr(PRIO_INFO, QgAddOnResultErrCode::Unknown, "Node1"); + assertEqual(errInfo.getPriority(), PRIO_INFO); + + QgAddOnResultErr errWarning = QgAddOnResultErr(PRIO_WARNING, QgAddOnResultErrCode::Unknown, "Node2"); + assertEqual(errWarning.getPriority(), PRIO_WARNING); + + QgAddOnResultErr errSevere = QgAddOnResultErr(PRIO_SEVERE, QgAddOnResultErrCode::Unknown, "Node3"); + assertEqual(errSevere.getPriority(), PRIO_SEVERE); + return 0; + } + + case "QgAddOnResultErr_getPriorityAsText": + { + QgAddOnResultErr errInfo = QgAddOnResultErr(PRIO_INFO, QgAddOnResultErrCode::Unknown, "Node"); + assertEqual(errInfo.getPriorityAsText(), "Info"); + + QgAddOnResultErr errWarning = QgAddOnResultErr(PRIO_WARNING, QgAddOnResultErrCode::Unknown, "Node"); + assertEqual(errWarning.getPriorityAsText(), "Warning"); + + QgAddOnResultErr errSevere = QgAddOnResultErr(PRIO_SEVERE, QgAddOnResultErrCode::Unknown, "Node"); + assertEqual(errSevere.getPriorityAsText(), "Error"); + return 0; + } + + case "QgAddOnResultErr_toMap": + { + QgAddOnResultErr err = QgAddOnResultErr(PRIO_WARNING, QgAddOnResultErrCode::Unknown, "TestNode"); + mapping map = err.toMap(); + + // Map should have priority as key + assertTrue(mappingHasKey(map, "Warning")); + assertNotEqual(map["Warning"], ""); + return 0; + } + } + + return -1; + } +}; + +//-------------------------------------------------------------------------------- +main() +{ + TstQgAddOnResultErr test; + test.startAll(); + exit(0); +} diff --git a/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/QgBase.ctl b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/QgBase.ctl new file mode 100644 index 00000000..c02df0ee --- /dev/null +++ b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/QgBase.ctl @@ -0,0 +1,88 @@ +//-------------------------------------------------------------------------------- +/** + @file $relPath + @copyright Copyright 2026 SIEMENS AG + SPDX-License-Identifier: GPL-3.0-only +*/ + +/*! + * @brief Tests for class: QgBase (base class tests) + */ + +//-------------------------------------------------------------------------------- +// used libraries (#uses) +#uses "classes/QualityGates/QgBase" /*!< tested object */ +#uses "classes/oaTest/OaTest" + +//-------------------------------------------------------------------------------- +class TstQgBase : OaTest +{ + public dyn_string getAllTestCaseIds() + { + return makeDynString( + "QgBaseError_enum_values", + "QgBase_getStoreFields", + "QgBase_calculateState_no_error", + "QgBase_calculateState_with_error" + ); + } + + protected int startTestCase(const string tcId) + { + switch (tcId) + { + case "QgBaseError_enum_values": + { + // Test enum values are defined + assertTrue((int)QgBaseError::Exception > 0); + assertTrue((int)QgBaseError::NotImplemented > 0); + assertTrue((int)QgBaseError::Start > 0); + assertTrue((int)QgBaseError::Calculate > 0); + assertTrue((int)QgBaseError::Validate > 0); + assertTrue((int)QgBaseError::Done > 0); + return 0; + } + + case "QgBase_getStoreFields": + { + dyn_string fields = QgBase::getStoreFields(); + assertEqual(dynlen(fields), 6); + assertTrue(dynContains(fields, "value") > 0); + assertTrue(dynContains(fields, "descr") > 0); + assertTrue(dynContains(fields, "goodRange") > 0); + assertTrue(dynContains(fields, "totalPoints") > 0); + assertTrue(dynContains(fields, "errorPoints") > 0); + assertTrue(dynContains(fields, "reason") > 0); + return 0; + } + + case "QgBase_calculateState_no_error": + { + shared_ptr result = new QgVersionResult(); + result.hasError = FALSE; + QgResultState state = QgBase::calculateState(result); + assertEqual(state, QgResultState::success); + return 0; + } + + case "QgBase_calculateState_with_error": + { + shared_ptr result = new QgVersionResult(); + result.hasError = TRUE; + QgResultState state = QgBase::calculateState(result); + assertEqual(state, QgResultState::warning); + return 0; + } + } + + return -1; + } +}; + +//-------------------------------------------------------------------------------- +main() +{ + TstQgBase test; + test.startAll(); + exit(0); +} diff --git a/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/QgMsgCat.ctl b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/QgMsgCat.ctl new file mode 100644 index 00000000..6b982ceb --- /dev/null +++ b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/QgMsgCat.ctl @@ -0,0 +1,137 @@ +//-------------------------------------------------------------------------------- +/** + @file $relPath + @copyright Copyright 2026 SIEMENS AG + SPDX-License-Identifier: GPL-3.0-only +*/ + +/*! + * @brief Tests for class: QgMsgCat + */ + +//-------------------------------------------------------------------------------- +// used libraries (#uses) +#uses "classes/QualityGates/QgMsgCat" /*!< tested object */ +#uses "classes/oaTest/OaTest" + +//-------------------------------------------------------------------------------- +class TstQgMsgCat : OaTest +{ + public dyn_string getAllTestCaseIds() + { + return makeDynString( + "QgMsgCat_ctor_default", + "QgMsgCat_ctor_with_name", + "QgMsgCat_setName_getName", + "QgMsgCat_setPrio", + "QgMsgCat_getPriorityAsText_Info", + "QgMsgCat_getPriorityAsText_Warning", + "QgMsgCat_getPriorityAsText_Error", + "QgMsgCat_getPriorityAsText_Unknown", + "QgMsgCat_getText_dollar_replacement" + ); + } + + protected int startTestCase(const string tcId) + { + switch (tcId) + { + case "QgMsgCat_ctor_default": + { + QgMsgCat msgCat; + + assertEqual(msgCat.getName(), ""); + return 0; + } + + case "QgMsgCat_ctor_with_name": + { + QgMsgCat msgCat = QgMsgCat("TestCatalog"); + + assertEqual(msgCat.getName(), "TestCatalog"); + return 0; + } + + case "QgMsgCat_setName_getName": + { + QgMsgCat msgCat; + + msgCat.setName("MyCatalog"); + assertEqual(msgCat.getName(), "MyCatalog"); + + msgCat.setName("AnotherCatalog"); + assertEqual(msgCat.getName(), "AnotherCatalog"); + return 0; + } + + case "QgMsgCat_setPrio": + { + QgMsgCat msgCat; + + // setPrio should not throw + msgCat.setPrio(QgMsgCatErrPrio::Info); + msgCat.setPrio(QgMsgCatErrPrio::Warning); + msgCat.setPrio(QgMsgCatErrPrio::Error); + return 0; + } + + case "QgMsgCat_getPriorityAsText_Info": + { + QgMsgCat msgCat; + + assertEqual(msgCat.getPriorityAsText(QgMsgCatErrPrio::Info), "Info"); + return 0; + } + + case "QgMsgCat_getPriorityAsText_Warning": + { + QgMsgCat msgCat; + + assertEqual(msgCat.getPriorityAsText(QgMsgCatErrPrio::Warning), "Warning"); + return 0; + } + + case "QgMsgCat_getPriorityAsText_Error": + { + QgMsgCat msgCat; + + assertEqual(msgCat.getPriorityAsText(QgMsgCatErrPrio::Error), "Error"); + return 0; + } + + case "QgMsgCat_getPriorityAsText_Unknown": + { + QgMsgCat msgCat; + + // Invalid enum value should return "Unkwon" (typo in original code) + assertEqual(msgCat.getPriorityAsText((99), "Unkwon"); + return 0; + } + + case "QgMsgCat_getText_dollar_replacement": + { + QgMsgCat msgCat = QgMsgCat("NonExistentCatalog"); + + // When catalog doesn't exist, getText returns the key itself + // But we can test dollar replacement on the key + mapping dollars = makeMapping("var1", "value1", "var2", 42); + string result = msgCat.getText("test.$var1.$var2.end", dollars); + + // The key should have $var1 and $var2 replaced + assertTrue(strpos(result, "value1") >= 0); + assertTrue(strpos(result, "42") >= 0); + return 0; + } + } + + return -1; + } +}; + +//-------------------------------------------------------------------------------- +main() +{ + TstQgMsgCat test; + test.startAll(); + exit(0); +} diff --git a/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/QgResultPublisher.ctl b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/QgResultPublisher.ctl new file mode 100644 index 00000000..6f930c4e --- /dev/null +++ b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/QgResultPublisher.ctl @@ -0,0 +1,99 @@ +//-------------------------------------------------------------------------------- +/** + @file $relPath + @copyright Copyright 2026 SIEMENS AG + SPDX-License-Identifier: GPL-3.0-only +*/ + +/*! + * @brief Tests for class: QgResultPublisher + */ + +//-------------------------------------------------------------------------------- +// used libraries (#uses) +#uses "classes/QualityGates/QgResultPublisher" /*!< tested object */ +#uses "classes/oaTest/OaTest" + +//-------------------------------------------------------------------------------- +class TstQgResultPublisher : OaTest +{ + public dyn_string getAllTestCaseIds() + { + return makeDynString( + "QgResultState_enum_values", + "QgResultPublisher_ctor", + "QgResultPublisher_stateToString_success", + "QgResultPublisher_stateToString_warning", + "QgResultPublisher_stateToString_error", + "QgResultPublisher_jsonFormat_default", + "QgVersionResultJsonFormat_enum_values" + ); + } + + protected int startTestCase(const string tcId) + { + switch (tcId) + { + case "QgResultState_enum_values": + { + // Test enum values exist and are distinct + assertTrue((int)QgResultState::success != (int)QgResultState::warning); + assertTrue((int)QgResultState::warning != (int)QgResultState::error); + assertTrue((int)QgResultState::success != (int)QgResultState::error); + return 0; + } + + case "QgResultPublisher_ctor": + { + QgResultPublisher publisher; + // Constructor should initialize fields + assertEqual(dynlen(publisher.fields), 0); + return 0; + } + + case "QgResultPublisher_stateToString_success": + { + assertEqual(QgResultPublisher::stateToString(QgResultState::success), "success"); + return 0; + } + + case "QgResultPublisher_stateToString_warning": + { + assertEqual(QgResultPublisher::stateToString(QgResultState::warning), "warning"); + return 0; + } + + case "QgResultPublisher_stateToString_error": + { + assertEqual(QgResultPublisher::stateToString(QgResultState::error), "error"); + return 0; + } + + case "QgResultPublisher_jsonFormat_default": + { + // Static member should be Compact by default + assertEqual(QgResultPublisher::jsonFormat, QgVersionResultJsonFormat::Compact); + return 0; + } + + case "QgVersionResultJsonFormat_enum_values": + { + // Test both formats exist + QgVersionResultJsonFormat fmt1 = QgVersionResultJsonFormat::Compact; + QgVersionResultJsonFormat fmt2 = QgVersionResultJsonFormat::Indented; + assertTrue((int)fmt1 != (int)fmt2); + return 0; + } + } + + return -1; + } +}; + +//-------------------------------------------------------------------------------- +main() +{ + TstQgResultPublisher test; + test.startAll(); + exit(0); +} diff --git a/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/QgStaticCheck/CtrlCode/QgCtrlCodeScriptData.ctl b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/QgStaticCheck/CtrlCode/QgCtrlCodeScriptData.ctl new file mode 100644 index 00000000..ec589e59 --- /dev/null +++ b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/QgStaticCheck/CtrlCode/QgCtrlCodeScriptData.ctl @@ -0,0 +1,264 @@ +//-------------------------------------------------------------------------------- +/** + @file $relPath + @copyright Copyright 2024 SIEMENS AG + SPDX-License-Identifier: GPL-3.0-only +*/ + +/*! + * @brief Tests for class: QgCtrlCodeScriptData + * + * @details This test focuses on the basic functionality of QgCtrlCodeScriptData + * without requiring external tools like Python/Lizard. + * Tests cover: + * - Constructor behavior + * - Getter methods (default values) + * - Path/name handling + * - Average calculations (edge cases) + * - Static methods for settings defaults + */ + +//-------------------------------------------------------------------------------- +// used libraries (#uses) +#uses "classes/QualityGates/QgVersionResult" /*!< Required dependency for QgCtrlCodeScriptData */ +#uses "classes/QualityGates/QgStaticCheck/CtrlCode/QgCtrlCodeScriptData" /*!< tested object */ +#uses "classes/oaTest/OaTest" + +//-------------------------------------------------------------------------------- +class TstQgCtrlCodeScriptData : OaTest +{ + public dyn_string getAllTestCaseIds() + { + // Note: Tests for getMaxCountOfFunctions, getMinCountOfFunctions, getMaxNLOC, + // getMinNLOC, getMaxAvgCCN are excluded because they depend on QgSettings + // which requires a settings JSON file that isn't available in PACK_SEL: 2 mode. + return makeDynString( + "QgCtrlCodeScriptData_ctorDefault", + "QgCtrlCodeScriptData_ctorWithPath", + "QgCtrlCodeScriptData_setPath", + "QgCtrlCodeScriptData_getName", + "QgCtrlCodeScriptData_getNameEmptyPath", + "QgCtrlCodeScriptData_isCalculated_default", + "QgCtrlCodeScriptData_getCountOfFunctions_default", + "QgCtrlCodeScriptData_getCountOfLines_default", + "QgCtrlCodeScriptData_getCCN_default", + "QgCtrlCodeScriptData_getNLOC_default", + "QgCtrlCodeScriptData_getAvgCCN_noFunctions", + "QgCtrlCodeScriptData_getAvgNLOC_noFunctions", + "QgCtrlCodeScriptData_getAvgLines_default", + "QgCtrlCodeScriptData_getAvgParamCount_default", + "QgCtrlCodeScriptData_calculate_fileNotExists" + ); + } + + //------------------------------------------------------------------------------ + protected int startTestCase(const string tcId) + { + switch (tcId) + { + case "QgCtrlCodeScriptData_ctorDefault": + { + // Test default constructor without arguments + QgCtrlCodeScriptData data; + + assertFalse(data.isCalculated(), "Default: should not be calculated"); + assertEqual(data.getName(), "", "Default: name should be empty"); + assertEqual(data.getCountOfFunctions(), 0, "Default: function count should be 0"); + assertEqual(data.getCountOfLines(), 0, "Default: line count should be 0"); + assertEqual(data.getCCN(), 0, "Default: CCN should be 0"); + assertEqual(data.getNLOC(), 0, "Default: NLOC should be 0"); + return 0; + } + + case "QgCtrlCodeScriptData_ctorWithPath": + { + // Test constructor with file path + const string testPath = "C:/projects/test/scripts/myScript.ctl"; + QgCtrlCodeScriptData data = QgCtrlCodeScriptData(testPath); + + assertEqual(data.getName(), "myScript.ctl", "Should extract filename from path"); + assertFalse(data.isCalculated(), "Should not be calculated after construction"); + return 0; + } + + case "QgCtrlCodeScriptData_setPath": + { + // Test setPath method + QgCtrlCodeScriptData data; + + assertEqual(data.getName(), "", "Initially name should be empty"); + + data.setPath("D:/workspace/libs/testLib.ctl"); + assertEqual(data.getName(), "testLib.ctl", "Name should be updated after setPath"); + + // Test path change + data.setPath("E:/other/folder/anotherScript.ctl"); + assertEqual(data.getName(), "anotherScript.ctl", "Name should reflect new path"); + return 0; + } + + case "QgCtrlCodeScriptData_getName": + { + // Test getName with various path formats + QgCtrlCodeScriptData data1 = QgCtrlCodeScriptData("C:/path/to/file.ctl"); + assertEqual(data1.getName(), "file.ctl", "Windows path with forward slashes"); + + QgCtrlCodeScriptData data2 = QgCtrlCodeScriptData("relative/path/script.ctl"); + assertEqual(data2.getName(), "script.ctl", "Relative path"); + + QgCtrlCodeScriptData data3 = QgCtrlCodeScriptData("justfile.ctl"); + assertEqual(data3.getName(), "justfile.ctl", "Just filename"); + return 0; + } + + case "QgCtrlCodeScriptData_getNameEmptyPath": + { + // Test getName when path is empty + QgCtrlCodeScriptData data; + assertEqual(data.getName(), "", "Empty path should return empty name"); + return 0; + } + + case "QgCtrlCodeScriptData_isCalculated_default": + { + // Test that isCalculated returns false by default + QgCtrlCodeScriptData data = QgCtrlCodeScriptData("C:/test/script.ctl"); + assertFalse(data.isCalculated(), "Should be false before calculate() is called"); + return 0; + } + + case "QgCtrlCodeScriptData_getCountOfFunctions_default": + { + // Test getCountOfFunctions returns 0 before calculation + QgCtrlCodeScriptData data; + assertEqual(data.getCountOfFunctions(), 0, "Default function count should be 0"); + return 0; + } + + case "QgCtrlCodeScriptData_getCountOfLines_default": + { + // Test getCountOfLines returns 0 before calculation + QgCtrlCodeScriptData data; + assertEqual(data.getCountOfLines(), 0, "Default line count should be 0"); + return 0; + } + + case "QgCtrlCodeScriptData_getCCN_default": + { + // Test getCCN returns 0 before calculation + QgCtrlCodeScriptData data; + assertEqual(data.getCCN(), 0, "Default CCN should be 0"); + return 0; + } + + case "QgCtrlCodeScriptData_getNLOC_default": + { + // Test getNLOC returns 0 before calculation + QgCtrlCodeScriptData data; + assertEqual(data.getNLOC(), 0, "Default NLOC should be 0"); + return 0; + } + + case "QgCtrlCodeScriptData_getAvgCCN_noFunctions": + { + // Test getAvgCCN returns 0 when no functions (avoids division by zero) + QgCtrlCodeScriptData data; + assertEqual(data.getAvgCCN(), 0.0, "Avg CCN should be 0.0 when no functions"); + return 0; + } + + case "QgCtrlCodeScriptData_getAvgNLOC_noFunctions": + { + // Test getAvgNLOC returns 0 when no functions (avoids division by zero) + QgCtrlCodeScriptData data; + assertEqual(data.getAvgNLOC(), 0.0, "Avg NLOC should be 0.0 when no functions"); + return 0; + } + + case "QgCtrlCodeScriptData_getAvgLines_default": + { + // Test getAvgLines returns 0 by default + QgCtrlCodeScriptData data; + assertEqual(data.getAvgLines(), 0.0, "Default avg lines should be 0.0"); + return 0; + } + + case "QgCtrlCodeScriptData_getAvgParamCount_default": + { + // Test getAvgParamCount returns 0 by default + QgCtrlCodeScriptData data; + assertEqual(data.getAvgParamCount(), 0.0, "Default avg param count should be 0.0"); + return 0; + } + + case "QgCtrlCodeScriptData_getMaxCountOfFunctions": + { + // Test static getMaxCountOfFunctions returns reasonable default + // Note: QgSettings may throw if settings file not found, which uses default value + int maxFuncs = QgCtrlCodeScriptData::getMaxCountOfFunctions(); + // Default is 100 according to source (DEFAULT_FUNCCOUNT_HIGH) + assertTrue(maxFuncs > 0, "Max count of functions should be positive"); + // When QgSettings throws, getHighLimit returns the provided default (100) + return 0; + } + + case "QgCtrlCodeScriptData_getMinCountOfFunctions": + { + // Test static getMinCountOfFunctions returns reasonable default + int minFuncs = QgCtrlCodeScriptData::getMinCountOfFunctions(); + // Default is 0 according to source (DEFAULT_FUNCCOUNT_LOW) + assertTrue(minFuncs >= 0, "Min count of functions should be non-negative"); + return 0; + } + + case "QgCtrlCodeScriptData_getMaxNLOC": + { + // Test static getMaxNLOC returns reasonable default + int maxNLOC = QgCtrlCodeScriptData::getMaxNLOC(); + // Default is 600 according to source (DEFAULT_NLOC_HIGH) + assertTrue(maxNLOC > 0, "Max NLOC should be positive"); + return 0; + } + + case "QgCtrlCodeScriptData_getMinNLOC": + { + // Test static getMinNLOC returns reasonable default + int minNLOC = QgCtrlCodeScriptData::getMinNLOC(); + // Default is 1 according to source (DEFAULT_NLOC_LOW) + assertTrue(minNLOC >= 0, "Min NLOC should be non-negative"); + return 0; + } + + case "QgCtrlCodeScriptData_getMaxAvgCCN": + { + // Test static getMaxAvgCCN returns reasonable default + float maxAvgCCN = QgCtrlCodeScriptData::getMaxAvgCCN(); + // Default is 10.0 according to source (DEFAULT_AVGCCN_HIGH) + assertTrue(maxAvgCCN > 0.0, "Max avg CCN should be positive"); + return 0; + } + + case "QgCtrlCodeScriptData_calculate_fileNotExists": + { + // Test calculate() returns error for non-existent file + QgCtrlCodeScriptData data = QgCtrlCodeScriptData("C:/nonexistent/path/to/file.ctl"); + int rc = data.calculate(); + + assertEqual(rc, -1, "calculate() should return -1 for non-existent file"); + assertFalse(data.isCalculated(), "Should not be marked as calculated for missing file"); + return 0; + } + } + + return 0; + } +}; + +//-------------------------------------------------------------------------------- +// Register test +void main() +{ + TstQgCtrlCodeScriptData test = TstQgCtrlCodeScriptData(); + test.startAll(); + exit(0); +} diff --git a/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/QgTest.ctl b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/QgTest.ctl new file mode 100644 index 00000000..26f63684 --- /dev/null +++ b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/QgTest.ctl @@ -0,0 +1,144 @@ +//-------------------------------------------------------------------------------- +/** + @file $relPath + @copyright Copyright 2026 SIEMENS AG + SPDX-License-Identifier: GPL-3.0-only +*/ + +/*! + * @brief Tests for class: QgTest + */ + +//-------------------------------------------------------------------------------- +// used libraries (#uses) +#uses "classes/QualityGates/QgTest" /*!< tested object */ +#uses "classes/oaTest/OaTest" + +//-------------------------------------------------------------------------------- +class TstQgTest : OaTest +{ + public dyn_string getAllTestCaseIds() + { + return makeDynString( + "QgTest_ctor", + "QgTest_calculateScore_no_errors", + "QgTest_calculateScore_with_errors", + "QgTest_getErrorCount", + "QgTest_getAllCount", + "QgTest_getErrPrio_default", + "QgTest_getErrCode_default", + "QgTest_getErrNote_default" + ); + } + + protected int startTestCase(const string tcId) + { + switch (tcId) + { + case "QgTest_ctor": + { + QgTest qgTest; + + // Default values + assertEqual(qgTest._errCount, 0); + assertEqual(qgTest._all, 0); + return 0; + } + + case "QgTest_calculateScore_no_errors": + { + QgTest qgTest; + + // Simulate 10 tests, no errors + qgTest._all = 10; + qgTest._errCount = 0; + + float score = qgTest.calculateScore(); + assertEqual(score, 100.0); + return 0; + } + + case "QgTest_calculateScore_with_errors": + { + QgTest qgTest; + + // Simulate 10 tests, 2 errors -> 80% + qgTest._all = 10; + qgTest._errCount = 2; + + float score = qgTest.calculateScore(); + assertEqual(score, 80.0); + + // Simulate 10 tests, 5 errors -> 50% + qgTest._errCount = 5; + score = qgTest.calculateScore(); + assertEqual(score, 50.0); + + // Simulate all errors -> 0% + qgTest._errCount = 10; + score = qgTest.calculateScore(); + assertEqual(score, 0.0); + return 0; + } + + case "QgTest_getErrorCount": + { + QgTest qgTest; + + assertEqual(qgTest.getErrorCount(), 0); + + qgTest._errCount = 5; + assertEqual(qgTest.getErrorCount(), 5); + return 0; + } + + case "QgTest_getAllCount": + { + QgTest qgTest; + + assertEqual(qgTest.getAllCount(), 0); + + qgTest._all = 15; + assertEqual(qgTest.getAllCount(), 15); + return 0; + } + + case "QgTest_getErrPrio_default": + { + QgTest qgTest; + + // Default error priority should be PRIO_SEVERE + assertEqual(qgTest.getErrPrio(), PRIO_SEVERE); + return 0; + } + + case "QgTest_getErrCode_default": + { + QgTest qgTest; + + // Default error code is 1 + assertEqual(qgTest.getErrCode(), 1); + return 0; + } + + case "QgTest_getErrNote_default": + { + QgTest qgTest; + + // Default error note is empty + assertEqual(qgTest.getErrNote(), ""); + return 0; + } + } + + return -1; + } +}; + +//-------------------------------------------------------------------------------- +main() +{ + TstQgTest test; + test.startAll(); + exit(0); +} diff --git a/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/QgVersionResult.ctl b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/QgVersionResult.ctl new file mode 100644 index 00000000..8660d6f5 --- /dev/null +++ b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/QgVersionResult.ctl @@ -0,0 +1,303 @@ +//-------------------------------------------------------------------------------- +/** + @file $relPath + @copyright Copyright 2026 SIEMENS AG + SPDX-License-Identifier: GPL-3.0-only +*/ + +/*! + * @brief Tests for struct: QgVersionResult + */ + +//-------------------------------------------------------------------------------- +// used libraries (#uses) +#uses "classes/QualityGates/QgVersionResult" /*!< tested object */ +#uses "classes/oaTest/OaTest" + +//-------------------------------------------------------------------------------- +class TstQgVersionResult : OaTest +{ + public dyn_string getAllTestCaseIds() + { + return makeDynString( + "QgVersionResult_ctor", + "QgVersionResult_setLocation", + "QgVersionResult_setAssertionText", + "QgVersionResult_setReasonText", + "QgVersionResult_assertGreatherEqual", + "QgVersionResult_assertLessEqual", + "QgVersionResult_assertEqual", + "QgVersionResult_assertTrue", + "QgVersionResult_assertFalse", + "QgVersionResult_assertBetween", + "QgVersionResult_assertDynContains", + "QgVersionResult_calculateScore", + "QgVersionResult_info" + ); + } + + protected int startTestCase(const string tcId) + { + switch (tcId) + { + case "QgVersionResult_ctor": + { + QgVersionResult result; + + assertEqual(result.hasError, FALSE); + assertEqual(result.totalPoints, 0); + assertEqual(result.errorPoints, 0); + assertEqual(result.text, ""); + assertEqual(result.location, ""); + return 0; + } + + case "QgVersionResult_setLocation": + { + QgVersionResult result; + + result.setLocation("/path/to/file.ctl:123"); + assertEqual(result.getLocation(), "/path/to/file.ctl:123"); + return 0; + } + + case "QgVersionResult_setAssertionText": + { + QgVersionResult result; + + result.setAssertionText("testKey"); + assertEqual(result.assertKey, "testKey"); + + mapping dollars = makeMapping("var1", "value1"); + result.setAssertionText("testKey2", dollars); + assertEqual(result.assertKey, "testKey2"); + assertTrue(mappingHasKey(result.assertDollars, "var1")); + return 0; + } + + case "QgVersionResult_setReasonText": + { + QgVersionResult result; + + result.setReasonText("reasonKey"); + assertEqual(result.reasonKey, "reasonKey"); + + mapping dollars = makeMapping("info", "detail"); + result.setReasonText("reasonKey2", dollars); + assertEqual(result.reasonKey, "reasonKey2"); + assertTrue(mappingHasKey(result.reasonDollars, "info")); + return 0; + } + + case "QgVersionResult_assertGreatherEqual": + { + QgVersionResult result; + + // 10 >= 5 should pass + bool passed = result.assertGreatherEqual(10, 5); + assertTrue(passed); + assertFalse(result.hasError); + assertEqual(result.totalPoints, 1); + assertEqual(result.errorPoints, 0); + + // 3 >= 5 should fail + QgVersionResult result2; + passed = result2.assertGreatherEqual(3, 5); + assertFalse(passed); + assertTrue(result2.hasError); + assertEqual(result2.totalPoints, 1); + assertEqual(result2.errorPoints, 1); + + // 5 >= 5 should pass (equal) + QgVersionResult result3; + passed = result3.assertGreatherEqual(5, 5); + assertTrue(passed); + assertFalse(result3.hasError); + return 0; + } + + case "QgVersionResult_assertLessEqual": + { + QgVersionResult result; + + // 3 <= 5 should pass + bool passed = result.assertLessEqual(3, 5); + assertTrue(passed); + assertFalse(result.hasError); + + // 10 <= 5 should fail + QgVersionResult result2; + passed = result2.assertLessEqual(10, 5); + assertFalse(passed); + assertTrue(result2.hasError); + + // 5 <= 5 should pass (equal) + QgVersionResult result3; + passed = result3.assertLessEqual(5, 5); + assertTrue(passed); + return 0; + } + + case "QgVersionResult_assertEqual": + { + QgVersionResult result; + + // 5 == 5 should pass + bool passed = result.assertEqual(5, 5); + assertTrue(passed); + assertFalse(result.hasError); + + // 5 == 10 should fail + QgVersionResult result2; + passed = result2.assertEqual(5, 10); + assertFalse(passed); + assertTrue(result2.hasError); + + // String equality + QgVersionResult result3; + passed = result3.assertEqual("test", "test"); + assertTrue(passed); + return 0; + } + + case "QgVersionResult_assertTrue": + { + QgVersionResult result; + + // TRUE should pass + bool passed = result.assertTrue(TRUE); + assertTrue(passed); + assertFalse(result.hasError); + + // FALSE should fail + QgVersionResult result2; + passed = result2.assertTrue(FALSE); + assertFalse(passed); + assertTrue(result2.hasError); + return 0; + } + + case "QgVersionResult_assertFalse": + { + QgVersionResult result; + + // FALSE should pass + bool passed = result.assertFalse(FALSE); + assertTrue(passed); + assertFalse(result.hasError); + + // TRUE should fail + QgVersionResult result2; + passed = result2.assertFalse(TRUE); + assertFalse(passed); + assertTrue(result2.hasError); + return 0; + } + + case "QgVersionResult_assertBetween": + { + QgVersionResult result; + + // 5 between 1 and 10 should pass + bool passed = result.assertBetween(5, 1, 10); + assertTrue(passed); + assertFalse(result.hasError); + + // 0 between 1 and 10 should fail (below lower) + QgVersionResult result2; + passed = result2.assertBetween(0, 1, 10); + assertFalse(passed); + assertTrue(result2.hasError); + + // 15 between 1 and 10 should fail (above upper) + QgVersionResult result3; + passed = result3.assertBetween(15, 1, 10); + assertFalse(passed); + assertTrue(result3.hasError); + + // Edge case: value equals lower bound + QgVersionResult result4; + passed = result4.assertBetween(1, 1, 10); + assertTrue(passed); + + // Edge case: value equals upper bound + QgVersionResult result5; + passed = result5.assertBetween(10, 1, 10); + assertTrue(passed); + return 0; + } + + case "QgVersionResult_assertDynContains": + { + QgVersionResult result; + dyn_string list = makeDynString("a", "b", "c"); + + // "b" in list should pass + bool passed = result.assertDynContains(list, "b"); + assertTrue(passed); + assertFalse(result.hasError); + + // "x" not in list should fail + QgVersionResult result2; + passed = result2.assertDynContains(list, "x"); + assertFalse(passed); + assertTrue(result2.hasError); + return 0; + } + + case "QgVersionResult_calculateScore": + { + QgVersionResult result; + + // No points yet + result.totalPoints = 0; + result.errorPoints = 0; + float score = result.calculateScore(); + assertEqual(score, 0.0); + + // 10 total, 0 errors -> 100% + result.totalPoints = 10; + result.errorPoints = 0; + score = result.calculateScore(); + assertEqual(score, 100.0); + + // 10 total, 2 errors -> 80% + result.totalPoints = 10; + result.errorPoints = 2; + score = result.calculateScore(); + assertEqual(score, 80.0); + + // 10 total, 10 errors -> 0% + result.totalPoints = 10; + result.errorPoints = 10; + score = result.calculateScore(); + assertEqual(score, 0.0); + return 0; + } + + case "QgVersionResult_info": + { + QgVersionResult result; + + // info() should not set error + bool passed = result.info(42, 2); + assertTrue(passed); + assertFalse(result.hasError); + assertEqual(result.value, "42"); + assertEqual(result.totalPoints, 2); + assertEqual(result.errorPoints, 0); + return 0; + } + } + + return -1; + } +}; + +//-------------------------------------------------------------------------------- +main() +{ + TstQgVersionResult test; + test.startAll(); + exit(0); +} diff --git a/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/Tools/CppCheck/CppCheckError.ctl b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/Tools/CppCheck/CppCheckError.ctl new file mode 100644 index 00000000..5f1477fe --- /dev/null +++ b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/Tools/CppCheck/CppCheckError.ctl @@ -0,0 +1,122 @@ +//-------------------------------------------------------------------------------- +/** + @file $relPath + @copyright Copyright 2026 SIEMENS AG + SPDX-License-Identifier: GPL-3.0-only +*/ + +/*! + * @brief Tests for struct: CppCheckError + */ + +//-------------------------------------------------------------------------------- +// used libraries (#uses) +#uses "classes/QualityGates/Tools/CppCheck/CppCheckError" /*!< tested object */ +#uses "classes/oaTest/OaTest" + +//-------------------------------------------------------------------------------- +class TstCppCheckError : OaTest +{ + public dyn_string getAllTestCaseIds() + { + return makeDynString( + "CppCheckError_default_values", + "CppCheckError_set_values", + "CppCheckError_toStdErrString_minimal", + "CppCheckError_toStdErrString_full" + ); + } + + protected int startTestCase(const string tcId) + { + switch (tcId) + { + case "CppCheckError_default_values": + { + CppCheckError err; + assertEqual(err.id, ""); + assertEqual(err.severity, ""); + assertEqual(err.msg, ""); + assertEqual(err.verbose, ""); + assertEqual(err.path, ""); + assertEqual(err.path0, ""); + assertEqual(err.line, 0); + assertEqual(err.cwe, 0); + assertEqual(err.knownBug, ""); + return 0; + } + + case "CppCheckError_set_values": + { + CppCheckError err; + err.id = "uninitvar"; + err.severity = "error"; + err.msg = "Uninitialized variable: x"; + err.verbose = "Uninitialized variable: x is used without being initialized"; + err.path = "/path/to/file.ctl"; + err.path0 = "/path/to/file.ctl"; + err.line = 42; + err.cwe = 457; + err.knownBug = "BUG-123"; + + assertEqual(err.id, "uninitvar"); + assertEqual(err.severity, "error"); + assertEqual(err.msg, "Uninitialized variable: x"); + assertEqual(err.line, 42); + assertEqual(err.cwe, 457); + assertEqual(err.knownBug, "BUG-123"); + return 0; + } + + case "CppCheckError_toStdErrString_minimal": + { + CppCheckError err; + err.id = "testId"; + err.severity = "warning"; + err.line = 10; + err.cwe = 100; + + string result = err.toStdErrString(); + + assertTrue(strpos(result, "ID: testId") >= 0); + assertTrue(strpos(result, "Severity: warning") >= 0); + assertTrue(strpos(result, "Line: 10") >= 0); + assertTrue(strpos(result, "CWE: 100") >= 0); + return 0; + } + + case "CppCheckError_toStdErrString_full": + { + CppCheckError err; + err.id = "nullPointer"; + err.severity = "error"; + err.msg = "Null pointer dereference"; + err.verbose = "Dereferencing a null pointer leads to undefined behavior"; + err.path = "/scripts/libs/test.ctl"; + err.line = 25; + err.cwe = 476; + + string result = err.toStdErrString(); + + assertTrue(strpos(result, "ID: nullPointer") >= 0); + assertTrue(strpos(result, "Severity: error") >= 0); + assertTrue(strpos(result, "Msg: Null pointer dereference") >= 0); + assertTrue(strpos(result, "Verbose: Dereferencing") >= 0); + assertTrue(strpos(result, "Path: /scripts/libs/test.ctl") >= 0); + assertTrue(strpos(result, "Line: 25") >= 0); + assertTrue(strpos(result, "CWE: 476") >= 0); + return 0; + } + } + + return -1; + } +}; + +//-------------------------------------------------------------------------------- +main() +{ + TstCppCheckError test; + test.startAll(); + exit(0); +} diff --git a/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/Tools/CppCheck/CppCheckSettings.ctl b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/Tools/CppCheck/CppCheckSettings.ctl new file mode 100644 index 00000000..27bf626c --- /dev/null +++ b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/QualityGates/Tools/CppCheck/CppCheckSettings.ctl @@ -0,0 +1,201 @@ +//-------------------------------------------------------------------------------- +/** + @file $relPath + @copyright Copyright 2026 SIEMENS AG + SPDX-License-Identifier: GPL-3.0-only +*/ + +/*! + * @brief Tests for class: CppCheckSettings + */ + +//-------------------------------------------------------------------------------- +// used libraries (#uses) +#uses "classes/QualityGates/Tools/CppCheck/CppCheckSettings" /*!< tested object */ +#uses "classes/oaTest/OaTest" + +//-------------------------------------------------------------------------------- +class TstCppCheckSettings : OaTest +{ + public dyn_string getAllTestCaseIds() + { + return makeDynString( + "CppCheckSettings_default_values", + "CppCheckSettings_addEnabled", + "CppCheckSettings_enableXmlFormat", + "CppCheckSettings_addRuleFile", + "CppCheckSettings_addLibraryFile", + "CppCheckSettings_toCmdLine_minimal", + "CppCheckSettings_toCmdLine_with_options" + ); + } + + protected int startTestCase(const string tcId) + { + switch (tcId) + { + case "CppCheckSettings_default_values": + { + CppCheckSettings settings; + + assertFalse(settings.enableLibCheck); + assertTrue(settings.inconclusive); + assertTrue(settings.includeSubProjects); + assertFalse(settings.verbose); + assertFalse(settings.errorList); + assertFalse(settings.inlineSuppressions); + assertEqual(settings.winccoaProjectName, PROJ); + return 0; + } + + case "CppCheckSettings_addEnabled": + { + CppCheckSettings settings; + + settings.addEnabled("all"); + dyn_string cmd = settings.toCmdLine(); + assertTrue(dynContains(cmd, "--enable=all") > 0); + + // Add another + CppCheckSettings settings2; + settings2.addEnabled("style"); + settings2.addEnabled("warning"); + cmd = settings2.toCmdLine(); + assertTrue(dynContains(cmd, "--enable=style,warning") > 0); + return 0; + } + + case "CppCheckSettings_enableXmlFormat": + { + CppCheckSettings settings; + + assertFalse(settings.isXmlOutEnabled()); + + settings.enableXmlFormat(TRUE); + assertTrue(settings.isXmlOutEnabled()); + + dyn_string cmd = settings.toCmdLine(); + assertTrue(dynContains(cmd, "--xml") > 0); + + settings.enableXmlFormat(FALSE); + assertFalse(settings.isXmlOutEnabled()); + return 0; + } + + case "CppCheckSettings_addRuleFile": + { + CppCheckSettings settings; + settings.includeSubProjects = FALSE; // avoid subproject paths in output + + string rulePath = "/path/to/rule.xml"; + settings.addRuleFile(rulePath); + + dyn_string cmd = settings.toCmdLine(); + bool found = FALSE; + for (int i = 1; i <= dynlen(cmd); i++) + { + if (strpos(cmd[i], "--rule-file=") == 0) + { + found = TRUE; + break; + } + } + assertTrue(found); + + // Adding same file again should not duplicate + settings.addRuleFile(rulePath); + int count = 0; + cmd = settings.toCmdLine(); + for (int i = 1; i <= dynlen(cmd); i++) + { + if (strpos(cmd[i], "--rule-file=") == 0) + count++; + } + assertEqual(count, 1); + return 0; + } + + case "CppCheckSettings_addLibraryFile": + { + CppCheckSettings settings; + settings.includeSubProjects = FALSE; + + string libPath = "/path/to/library.xml"; + settings.addLibraryFile(libPath); + + dyn_string cmd = settings.toCmdLine(); + bool found = FALSE; + for (int i = 1; i <= dynlen(cmd); i++) + { + if (strpos(cmd[i], "--library=") == 0) + { + found = TRUE; + break; + } + } + assertTrue(found); + + // Adding empty path should be ignored + int prevLen = dynlen(cmd); + settings.addLibraryFile(""); + cmd = settings.toCmdLine(); + assertEqual(dynlen(cmd), prevLen); + return 0; + } + + case "CppCheckSettings_toCmdLine_minimal": + { + CppCheckSettings settings; + settings.includeSubProjects = FALSE; + settings.inconclusive = FALSE; + + dyn_string cmd = settings.toCmdLine(); + + // Should contain project name + bool hasProject = FALSE; + for (int i = 1; i <= dynlen(cmd); i++) + { + if (strpos(cmd[i], "--winccoa-projectName=") == 0) + { + hasProject = TRUE; + break; + } + } + assertTrue(hasProject); + return 0; + } + + case "CppCheckSettings_toCmdLine_with_options": + { + CppCheckSettings settings; + settings.includeSubProjects = FALSE; + settings.inconclusive = TRUE; + settings.verbose = TRUE; + settings.inlineSuppressions = TRUE; + settings.enableLibCheck = TRUE; + settings.enableXmlFormat(TRUE); + settings.addEnabled("all"); + + dyn_string cmd = settings.toCmdLine(); + + assertTrue(dynContains(cmd, "--inconclusive") > 0); + assertTrue(dynContains(cmd, "-v") > 0); + assertTrue(dynContains(cmd, "--inline-suppr") > 0); + assertTrue(dynContains(cmd, "--check-library") > 0); + assertTrue(dynContains(cmd, "--xml") > 0); + assertTrue(dynContains(cmd, "--enable=all") > 0); + return 0; + } + } + + return -1; + } +}; + +//-------------------------------------------------------------------------------- +main() +{ + TstCppCheckSettings test; + test.startAll(); + exit(0); +} diff --git a/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/Variables/Mapping.ctl b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/Variables/Mapping.ctl new file mode 100644 index 00000000..7ad21570 --- /dev/null +++ b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/Variables/Mapping.ctl @@ -0,0 +1,129 @@ +//-------------------------------------------------------------------------------- +/** + @file $relPath + @copyright Copyright 2026 SIEMENS AG + SPDX-License-Identifier: GPL-3.0-only +*/ + +/*! + * @brief Tests for class: Mapping + */ + +//-------------------------------------------------------------------------------- +// used libraries (#uses) +#uses "classes/Variables/Mapping" /*!< tested object */ +#uses "classes/oaTest/OaTest" + +//-------------------------------------------------------------------------------- +class TstMapping : OaTest +{ + public dyn_string getAllTestCaseIds() + { + return makeDynString( + "Mapping_ctor_empty", + "Mapping_ctor_with_value", + "Mapping_get_set", + "Mapping_getAt_existing_key", + "Mapping_getAt_missing_key_default", + "Mapping_getAt_missing_key_custom_default", + "Mapping_opPlus" + ); + } + + protected int startTestCase(const string tcId) + { + switch (tcId) + { + case "Mapping_ctor_empty": + { + Mapping m; + assertEqual(mappinglen(m.get()), 0); + return 0; + } + + case "Mapping_ctor_with_value": + { + mapping input = makeMapping("key1", "value1", "key2", 42); + Mapping m = Mapping(input); + assertEqual(mappinglen(m.get()), 2); + assertEqual(m.get()["key1"], "value1"); + assertEqual(m.get()["key2"], 42); + return 0; + } + + case "Mapping_get_set": + { + Mapping m; + assertEqual(mappinglen(m.get()), 0); + + mapping newMap = makeMapping("a", 1, "b", 2); + m.set(newMap); + assertEqual(mappinglen(m.get()), 2); + assertEqual(m.get()["a"], 1); + assertEqual(m.get()["b"], 2); + return 0; + } + + case "Mapping_getAt_existing_key": + { + Mapping m = Mapping(makeMapping("name", "test", "count", 100)); + assertEqual(m.getAt("name"), "test"); + assertEqual(m.getAt("count"), 100); + return 0; + } + + case "Mapping_getAt_missing_key_default": + { + Mapping m = Mapping(makeMapping("exists", "yes")); + anytype result = m.getAt("missing"); + assertTrue(isNull(result)); + return 0; + } + + case "Mapping_getAt_missing_key_custom_default": + { + Mapping m = Mapping(makeMapping("exists", "yes")); + assertEqual(m.getAt("missing", "default_value"), "default_value"); + assertEqual(m.getAt("missing", -1), -1); + return 0; + } + + case "Mapping_opPlus": + { + Mapping m1 = Mapping(makeMapping("a", 1, "b", 2)); + Mapping m2 = Mapping(makeMapping("c", 3, "d", 4)); + + m1.opPlus(m2); + + mapping result = m1.get(); + assertEqual(mappinglen(result), 4); + assertEqual(result["a"], 1); + assertEqual(result["b"], 2); + assertEqual(result["c"], 3); + assertEqual(result["d"], 4); + + // Test overwriting existing keys + Mapping m3 = Mapping(makeMapping("x", 10)); + Mapping m4 = Mapping(makeMapping("x", 20, "y", 30)); + + m3.opPlus(m4); + + mapping result2 = m3.get(); + assertEqual(mappinglen(result2), 2); + assertEqual(result2["x"], 20); // overwritten + assertEqual(result2["y"], 30); + return 0; + } + } + + return -1; + } +}; + +//-------------------------------------------------------------------------------- +main() +{ + TstMapping test; + test.startAll(); + exit(0); +} diff --git a/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/Variables/String.ctl b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/Variables/String.ctl new file mode 100644 index 00000000..bd5f8ff6 --- /dev/null +++ b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/classes/Variables/String.ctl @@ -0,0 +1,111 @@ +//-------------------------------------------------------------------------------- +/** + @file $relPath + @copyright Copyright 2026 SIEMENS AG + SPDX-License-Identifier: GPL-3.0-only +*/ + +/*! + * @brief Tests for class: String + */ + +//-------------------------------------------------------------------------------- +// used libraries (#uses) +#uses "classes/Variables/String" /*!< tested object */ +#uses "classes/oaTest/OaTest" + +//-------------------------------------------------------------------------------- +class TstString : OaTest +{ + public dyn_string getAllTestCaseIds() + { + return makeDynString( + "String_ctor_empty", + "String_ctor_with_value", + "String_get_set", + "String_endsWith_true", + "String_endsWith_false", + "String_endsWith_empty" + ); + } + + protected int startTestCase(const string tcId) + { + switch (tcId) + { + case "String_ctor_empty": + { + String s; + assertEqual(s.get(), ""); + return 0; + } + + case "String_ctor_with_value": + { + String s = String("hello world"); + assertEqual(s.get(), "hello world"); + return 0; + } + + case "String_get_set": + { + String s; + assertEqual(s.get(), ""); + + s.set("test value"); + assertEqual(s.get(), "test value"); + + s.set("another"); + assertEqual(s.get(), "another"); + + s.set(); // reset to default + assertEqual(s.get(), ""); + return 0; + } + + case "String_endsWith_true": + { + String s = String("filename.ctl"); + assertTrue(s.endsWith(".ctl")); + assertTrue(s.endsWith("ctl")); + assertTrue(s.endsWith("l")); + assertTrue(s.endsWith("filename.ctl")); + + String path = String("/path/to/file.xml"); + assertTrue(path.endsWith(".xml")); + assertTrue(path.endsWith("file.xml")); + return 0; + } + + case "String_endsWith_false": + { + String s = String("filename.ctl"); + assertFalse(s.endsWith(".xml")); + assertFalse(s.endsWith("CTL")); // case sensitive + assertFalse(s.endsWith("filename")); + assertFalse(s.endsWith("x")); + return 0; + } + + case "String_endsWith_empty": + { + String s = String("test"); + assertTrue(s.endsWith("")); // empty string always matches + + String empty = String(""); + assertTrue(empty.endsWith("")); + return 0; + } + } + + return -1; + } +}; + +//-------------------------------------------------------------------------------- +main() +{ + TstString test; + test.startAll(); + exit(0); +} diff --git a/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/fileSys.ctl b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/fileSys.ctl index adf28e40..137ecc67 100644 --- a/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/fileSys.ctl +++ b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/scripts/tests/libs/fileSys.ctl @@ -1,14 +1,12 @@ //-------------------------------------------------------------------------------- /** @file $relPath - @copyright Copyright 2023 SIEMENS AG + @copyright Copyright 2026 SIEMENS AG SPDX-License-Identifier: GPL-3.0-only */ /*! - * @brief Tests for lib: fileSys - * - * @author lschopp + * @brief Tests for library: fileSys */ //-------------------------------------------------------------------------------- @@ -21,22 +19,27 @@ class TstFileSys : OaTest { public dyn_string getAllTestCaseIds() { - // list with our testcases - return makeDynString("fileSys getFileNamesRecursive"); + return makeDynString("fileSys"); } protected int startTestCase(const string tcId) { switch (tcId) { - case "fileSys getFileNamesRecursive": + case "fileSys": { - fclose(fopen(PROJ_PATH + LIBS_REL_PATH + "dummy.ctl", "w")); - assertEqual(getFileNamesRecursive(""), makeDynString()); - assertEqual(getFileNamesRecursive("non existin path"), makeDynString()); - assertEqual(dynlen(getFileNamesRecursive(PROJ_PATH + PANELS_REL_PATH, "panel*")), 0); - assertEqual(dynlen(getFileNamesRecursive(PROJ_PATH + LIBS_REL_PATH, "*.ctl", FILTER_DIRS)), 0); - assertEqual(getFileNamesRecursive(PROJ_PATH + LIBS_REL_PATH, "*.ctl"), makeDynString(makeNativePath(PROJ_PATH + LIBS_REL_PATH + "dummy.ctl"))); + dyn_string paths = getSubProjPathes(); + int n = dynlen(paths); + + assertTrue(n >= 0); + + // The function starts at getPath(..., 2) and appends consecutively. + if (n > 0) + { + assertEqual(paths[1], getPath("", "", -1, 2)); + assertEqual(paths[n], getPath("", "", -1, n + 1)); + } + return 0; } } @@ -48,6 +51,7 @@ class TstFileSys : OaTest //-------------------------------------------------------------------------------- main() { - TstFileSys test; + TstFileSys test = TstFileSys(); test.startAll(); + exit(0); } diff --git a/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/toDoTests.md b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/toDoTests.md new file mode 100644 index 00000000..0cf8ec64 --- /dev/null +++ b/WinCC_OA_Test/TestSuites/suite_Common/sub_unit/toDoTests.md @@ -0,0 +1,190 @@ +# Unit Test Coverage - ToDo List + +> **Generated:** 2026-01-29 +> **Test Coverage:** ~44% (19 of 43 libraries have tests) + +## Summary + +| Metric | Count | +|---------------------|-------| +| Total Libraries | 43 | +| Existing Tests | 19 | +| Missing Tests | 24 | + +--- + +## ✅ Existing Tests (19) + +| Library Path | Test Status | +|--------------|-------------| +| `fileSys.ctl` | ✅ Done | +| `classes/ErrorHdl/OaLogger.ctl` | ✅ Done | +| `classes/FileSys/QgDir.ctl` | ✅ Done | +| `classes/FileSys/QgFile.ctl` | ✅ Done | +| `classes/Math/Math.ctl` | ✅ Done | +| `classes/QualityGates/Qg.ctl` | ✅ Done | +| `classes/QualityGates/QgAddOnResultErr.ctl` | ✅ Done | +| `classes/QualityGates/QgBase.ctl` | ✅ Done | +| `classes/QualityGates/QgMsgCat.ctl` | ✅ Done | +| `classes/QualityGates/QgResultPublisher.ctl` | ✅ Done | +| `classes/QualityGates/QgTest.ctl` | ✅ Done | +| `classes/QualityGates/QgVersionResult.ctl` | ✅ Done | +| `classes/QualityGates/AddOn/FileSys/QgAddOnResultsDir.ctl` | ✅ Done | +| `classes/QualityGates/AddOn/Output/QgAddOnResult.ctl` | ✅ Done | +| `classes/QualityGates/Tools/CppCheck/CppCheckError.ctl` | ✅ Done | +| `classes/QualityGates/Tools/CppCheck/CppCheckSettings.ctl` | ✅ Done | +| `classes/Variables/Float.ctl` | ✅ Done | +| `classes/Variables/Mapping.ctl` | ✅ Done | +| `classes/Variables/String.ctl` | ✅ Done | + +--- + +## ❌ Missing Tests (24) + +> **Skipped:** `gedi/qualityCheck_ext.ctl`, `scriptEditor/ctrlPPCheck_ext.ctl` (UI extensions, not in `/scripts/libs/classes/`) + +### classes/QualityGates/ (1) + +| Library | Priority | Notes | +|---------|----------|-------| +| `classes/QualityGates/QgSettings.ctl` | High (⚠️ deps) | Settings parsing, needs JSON files | + +### classes/QualityGates/AddOn/ (1) + +| Library | Priority | Notes | +|---------|----------|-------| +| `classes/QualityGates/AddOn/Output/QgAddOnScore.ctl` | Medium | Scoring logic (deprecated) | + +### classes/QualityGates/QgCtrlppCheck/ (1) + +| Library | Priority | Notes | +|---------|----------|-------| +| `classes/QualityGates/QgCtrlppCheck/QgCtrlppCheck.ctl` | Medium | CtrlppCheck wrapper | + +### classes/QualityGates/QgOverloadedFilesCheck/ (1) + +| Library | Priority | Notes | +|---------|----------|-------| +| `classes/QualityGates/QgOverloadedFilesCheck/QgOverloadedFilesCheck.ctl` | Medium | Overloaded files check | + +### classes/QualityGates/QgStaticCheck/ (2) + +| Library | Priority | Notes | +|---------|----------|-------| +| `classes/QualityGates/QgStaticCheck/StaticCodeDir.ctl` | Medium | Directory scanning | +| `classes/QualityGates/QgStaticCheck/StaticDir.ctl` | Medium | Directory scanning | + +### classes/QualityGates/QgStaticCheck/CtrlCode/ (4) + +| Library | Priority | Notes | +|---------|----------|-------| +| `classes/QualityGates/QgStaticCheck/CtrlCode/FunctionData.ctl` | High (⚠️ deps) | Function metrics - has CtrlPv2Admin dep | +| `classes/QualityGates/QgStaticCheck/CtrlCode/QgCtrlCodeScriptData.ctl` | High | Script metrics, testable | +| `classes/QualityGates/QgStaticCheck/CtrlCode/ScriptFile.ctl` | Medium | Script file handling | +| `classes/QualityGates/QgStaticCheck/CtrlCode/ScriptsDir.ctl` | Medium | Directory scanning | + +### classes/QualityGates/QgStaticCheck/Panels/ (2) + +| Library | Priority | Notes | +|---------|----------|-------| +| `classes/QualityGates/QgStaticCheck/Panels/PanelCheck.ctl` | Medium | Panel validation | +| `classes/QualityGates/QgStaticCheck/Panels/PanelsDir.ctl` | Medium | Directory scanning | + +### classes/QualityGates/QgStaticCheck/Panels/PanelFile/ (3) + +| Library | Priority | Notes | +|---------|----------|-------| +| `classes/QualityGates/QgStaticCheck/Panels/PanelFile/PanelFile.ctl` | Medium | Panel file parsing | +| `classes/QualityGates/QgStaticCheck/Panels/PanelFile/PanelFileScript.ctl` | Medium | Panel script extraction | +| `classes/QualityGates/QgStaticCheck/Panels/PanelFile/PanelFileShape.ctl` | Medium | Shape analysis | + +### classes/QualityGates/QgStaticCheck/Pictures/ (2) + +| Library | Priority | Notes | +|---------|----------|-------| +| `classes/QualityGates/QgStaticCheck/Pictures/PicturesDir.ctl` | Low | Directory scanning | +| `classes/QualityGates/QgStaticCheck/Pictures/PicturesFile.ctl` | Low | Picture file handling | + +### classes/QualityGates/QgSyntaxCheck/ (1) + +| Library | Priority | Notes | +|---------|----------|-------| +| `classes/QualityGates/QgSyntaxCheck/QgSyntaxCheck.ctl` | Medium | Syntax check wrapper | + +### classes/QualityGates/Tools/CppCheck/ (1) + +| Library | Priority | Notes | +|---------|----------|-------| +| `classes/QualityGates/Tools/CppCheck/CppCheck.ctl` | High | CppCheck integration, testable | + +### classes/QualityGates/Tools/Lizard/ (1) + +| Library | Priority | Notes | +|---------|----------|-------| +| `classes/QualityGates/Tools/Lizard/ToolLizard.ctl` | Medium | Lizard integration | + +### classes/QualityGates/Tools/OaSyntaxCheck/ (1) + +| Library | Priority | Notes | +|---------|----------|-------| +| `classes/QualityGates/Tools/OaSyntaxCheck/OaSyntaxCheck.ctl` | Medium | OA syntax check | + +### classes/QualityGates/Tools/Python/ (1) + +| Library | Priority | Notes | +|---------|----------|-------| +| `classes/QualityGates/Tools/Python/Python.ctl` | Low | Python path detection | + +--- + +## Recommended Test Order (by priority) + +### High Priority (Pure logic, easy to test) + +1. `classes/QualityGates/QgSettings.ctl` (⚠️ needs JSON file access) +2. `classes/QualityGates/QgStaticCheck/CtrlCode/QgCtrlCodeScriptData.ctl` + +> **✅ Completed:** CppCheckError.ctl, CppCheckSettings.ctl, QgAddOnResultErr.ctl, QgTest.ctl, QgVersionResult.ctl + +> **⚠️ Note:** FunctionData.ctl has CtrlPv2Admin dependency - not testable with PACK_SEL: 2 +> **⚠️ Note:** CppCheck.ctl has panel/CtrlXml/CtrlPv2Admin dependencies - not testable + +### Medium Priority + +1. `classes/QualityGates/QgBase.ctl` +2. `classes/QualityGates/QgMsgCat.ctl` +3. `classes/QualityGates/AddOn/Output/QgAddOnResult.ctl` +4. `classes/QualityGates/AddOn/Output/QgAddOnScore.ctl` +5. `classes/QualityGates/QgCtrlppCheck/QgCtrlppCheck.ctl` +6. `classes/QualityGates/QgOverloadedFilesCheck/QgOverloadedFilesCheck.ctl` +7. `classes/QualityGates/QgStaticCheck/StaticCodeDir.ctl` +8. `classes/QualityGates/QgStaticCheck/StaticDir.ctl` +9. `classes/QualityGates/QgStaticCheck/CtrlCode/ScriptFile.ctl` +10. `classes/QualityGates/QgStaticCheck/CtrlCode/ScriptsDir.ctl` +11. `classes/QualityGates/QgStaticCheck/Panels/PanelCheck.ctl` +12. `classes/QualityGates/QgStaticCheck/Panels/PanelsDir.ctl` +13. `classes/QualityGates/QgStaticCheck/Panels/PanelFile/PanelFile.ctl` +14. `classes/QualityGates/QgStaticCheck/Panels/PanelFile/PanelFileScript.ctl` +15. `classes/QualityGates/QgStaticCheck/Panels/PanelFile/PanelFileShape.ctl` +16. `classes/QualityGates/QgSyntaxCheck/QgSyntaxCheck.ctl` +17. `classes/QualityGates/Tools/Lizard/ToolLizard.ctl` +18. `classes/QualityGates/Tools/OaSyntaxCheck/OaSyntaxCheck.ctl` + +### Low Priority (UI/external dependencies) + +1. `classes/QualityGates/QgResultPublisher.ctl` +2. `classes/QualityGates/AddOn/FileSys/QgAddOnResultsDir.ctl` +3. `classes/QualityGates/QgStaticCheck/Pictures/PicturesDir.ctl` +4. `classes/QualityGates/QgStaticCheck/Pictures/PicturesFile.ctl` +5. `classes/QualityGates/Tools/Python/Python.ctl` +6. `gedi/qualityCheck_ext.ctl` +7. `scriptEditor/ctrlPPCheck_ext.ctl` + +--- + +## Notes + +- **PACK_SEL: 2** → No database access (`dp*()` functions unavailable) +- Tests must be registered in `testProj.unit.config` under `TEST_MANAGERS` +- Use `_throw()` mocking pattern for code that calls `fatal()`/`throwError()` +- See [createUnitTests-promt.md](createUnitTests-promt.md) for test creation guide diff --git a/WinCC_OA_Test/TestSuites/suite_Common/testProj.unit.config b/WinCC_OA_Test/TestSuites/suite_Common/testProj.unit.config index b11b2594..b26a2638 100644 --- a/WinCC_OA_Test/TestSuites/suite_Common/testProj.unit.config +++ b/WinCC_OA_Test/TestSuites/suite_Common/testProj.unit.config @@ -10,8 +10,23 @@ "TEST_MANAGERS":[ { "MANAGER_OPTIONS":"tests/libs/fileSys.ctl -n" }, { "MANAGER_OPTIONS":"tests/libs/classes/FileSys/QgDir.ctl -n" }, + { "MANAGER_OPTIONS":"tests/libs/classes/FileSys/QgFile.ctl -n" }, { "MANAGER_OPTIONS":"tests/libs/classes/Variables/Float.ctl -n" }, + { "MANAGER_OPTIONS":"tests/libs/classes/Variables/Mapping.ctl -n" }, + { "MANAGER_OPTIONS":"tests/libs/classes/Variables/String.ctl -n" }, + { "MANAGER_OPTIONS":"tests/libs/classes/ErrorHdl/OaLogger.ctl -n" }, { "MANAGER_OPTIONS":"tests/libs/classes/QualityGates/Qg.ctl -n" }, - { "MANAGER_OPTIONS":"tests/libs/classes/Math/Math.ctl -n" } + { "MANAGER_OPTIONS":"tests/libs/classes/Math/Math.ctl -n" }, + { "MANAGER_OPTIONS":"tests/libs/classes/QualityGates/Tools/CppCheck/CppCheckError.ctl -n" }, + { "MANAGER_OPTIONS":"tests/libs/classes/QualityGates/Tools/CppCheck/CppCheckSettings.ctl -n" }, + { "MANAGER_OPTIONS":"tests/libs/classes/QualityGates/QgAddOnResultErr.ctl -n" }, + { "MANAGER_OPTIONS":"tests/libs/classes/QualityGates/QgTest.ctl -n" }, + { "MANAGER_OPTIONS":"tests/libs/classes/QualityGates/QgVersionResult.ctl -n" }, + { "MANAGER_OPTIONS":"tests/libs/classes/QualityGates/QgMsgCat.ctl -n" }, + { "MANAGER_OPTIONS":"tests/libs/classes/QualityGates/QgBase.ctl -n" }, + { "MANAGER_OPTIONS":"tests/libs/classes/QualityGates/QgResultPublisher.ctl -n" }, + { "MANAGER_OPTIONS":"tests/libs/classes/QualityGates/AddOn/Output/QgAddOnResult.ctl -n" }, + { "MANAGER_OPTIONS":"tests/libs/classes/QualityGates/AddOn/FileSys/QgAddOnResultsDir.ctl -n" }, + { "MANAGER_OPTIONS":"tests/libs/classes/QualityGates/QgStaticCheck/CtrlCode/QgCtrlCodeScriptData.ctl -n" } ] } \ No newline at end of file diff --git a/WinCC_OA_Test/TestSuites/suite_CtrlppCheck/sub_app/data/references/variableNaming.xml b/WinCC_OA_Test/TestSuites/suite_CtrlppCheck/sub_app/data/references/variableNaming.xml index b360bb27..d3bfac68 100644 Binary files a/WinCC_OA_Test/TestSuites/suite_CtrlppCheck/sub_app/data/references/variableNaming.xml and b/WinCC_OA_Test/TestSuites/suite_CtrlppCheck/sub_app/data/references/variableNaming.xml differ diff --git a/WinCC_OA_Test/TestSuites/suite_CtrlppCheck/sub_app/data/references/vector.xml b/WinCC_OA_Test/TestSuites/suite_CtrlppCheck/sub_app/data/references/vector.xml index 2dc35441..be3fcad1 100644 Binary files a/WinCC_OA_Test/TestSuites/suite_CtrlppCheck/sub_app/data/references/vector.xml and b/WinCC_OA_Test/TestSuites/suite_CtrlppCheck/sub_app/data/references/vector.xml differ diff --git a/WinCC_OA_Test/TestSuites/suite_CtrlppCheck/sub_app/scripts/testScripts/autovar.ctl b/WinCC_OA_Test/TestSuites/suite_CtrlppCheck/sub_app/scripts/testScripts/autovar.ctl index 4bf3aed7..5dc8b3a6 100644 --- a/WinCC_OA_Test/TestSuites/suite_CtrlppCheck/sub_app/scripts/testScripts/autovar.ctl +++ b/WinCC_OA_Test/TestSuites/suite_CtrlppCheck/sub_app/scripts/testScripts/autovar.ctl @@ -1,7 +1,7 @@ // // tests for CheckAutoVariables // CheckAutoVariables::assignFunctionArg -#uses "stdVar" +#uses "std" struct S { diff --git a/WinCC_OA_Test/TestSuites/suite_CtrlppCheck/sub_app/scripts/testScripts/vector.ctl b/WinCC_OA_Test/TestSuites/suite_CtrlppCheck/sub_app/scripts/testScripts/vector.ctl index fc9828a9..7118b3f1 100644 --- a/WinCC_OA_Test/TestSuites/suite_CtrlppCheck/sub_app/scripts/testScripts/vector.ctl +++ b/WinCC_OA_Test/TestSuites/suite_CtrlppCheck/sub_app/scripts/testScripts/vector.ctl @@ -1,7 +1,7 @@ // start options: // error id: // support for WinCC OA datatype vector -#uses "stdVar" +#uses "std" void main() { diff --git a/devTools/ctlCoverageReport/coverage_report.js b/devTools/ctlCoverageReport/coverage_report.js new file mode 100644 index 00000000..0cc96fca --- /dev/null +++ b/devTools/ctlCoverageReport/coverage_report.js @@ -0,0 +1,310 @@ +#!/usr/bin/env node +/** + * WinCC OA Coverage Report Generator + * Merges multiple coverage XML files and generates a command-line report. + * + * Usage: node coverage_report.js [path] [-f filter] [-v] [-o output.xml] + */ + +const fs = require('fs'); +const path = require('path'); + +function createFileData() { + return { + lines: new Map(), + functions: new Map(), + branches: { total: 0, executed: 0 } + }; +} + +function parseCoverageXml(xmlContent, coverageData, filterPath) { + // Simple XML parsing using regex (avoiding external dependencies) + const scriptRegex = /