>,_fn_(*)>::operator="
+ }
+ }
+ },
+ {
+ "nestingLevel": 2,
+ "location": {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "file:/usr/include/c++/13/bits/unique_ptr.h",
+ "uriBaseId": "/mnt/c/Users/jonat/Code/TestCPP/usr/include/c++/13/bits/unique_ptr.h"
+ },
+ "region": {
+ "startLine": 442,
+ "startColumn": 2
+ }
+ },
+ "message": {
+ "text": "in call to `std::unique_ptr::reset(T*)` (modelled)"
+ }
+ }
+ },
+ {
+ "nestingLevel": 2,
+ "location": {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "file:/usr/include/c++/13/bits/unique_ptr.h",
+ "uriBaseId": "/mnt/c/Users/jonat/Code/TestCPP/usr/include/c++/13/bits/unique_ptr.h"
+ },
+ "region": {
+ "startLine": 442,
+ "startColumn": 2
+ }
+ },
+ "message": {
+ "text": "invalid access occurs here"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "file:src/TestCPPTestCase.cpp",
+ "uriBaseId": "/mnt/c/Users/jonat/Code/TestCPP/src/TestCPPTestCase.cpp"
+ },
+ "region": {
+ "startLine": 204,
+ "startColumn": 17
+ }
+ }
+ }
+ ],
+ "fingerprints": {
+ "hash/v1": "7a09e3db5fda863aff928a8b56e14543",
+ "key": "TestCPPTestCase.cpp|__infer_inner_destructor_~TestCase|USE_AFTER_DELETE"
+ }
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/data/sarif/join/cppcheck__mnt_c_code_project_src_Type1.cpp.sarif b/test/data/sarif/join/cppcheck__mnt_c_code_project_src_Type1.cpp.sarif
new file mode 100644
index 0000000..1be9997
--- /dev/null
+++ b/test/data/sarif/join/cppcheck__mnt_c_code_project_src_Type1.cpp.sarif
@@ -0,0 +1,167 @@
+{
+ "$schema": "https:\/\/docs.oasis-open.org\/sarif\/sarif\/v2.1.0\/errata01\/os\/schemas\/sarif-schema-2.1.0.json",
+ "runs": [
+ {
+ "results": [
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/include\/internal\/Type1.h"
+ },
+ "region": {
+ "endColumn": 24,
+ "endLine": 78,
+ "startColumn": 24,
+ "startLine": 78
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Function parameter 'failureMessage' should be passed by const reference."
+ },
+ "ruleId": "passedByValue"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/include\/internal\/Type1.h"
+ },
+ "region": {
+ "endColumn": 24,
+ "endLine": 109,
+ "startColumn": 24,
+ "startLine": 109
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Function parameter 'failureMessage' should be passed by const reference."
+ },
+ "ruleId": "passedByValue"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/include\/internal\/Type1.h"
+ },
+ "region": {
+ "endColumn": 24,
+ "endLine": 136,
+ "startColumn": 24,
+ "startLine": 136
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Function parameter 'failureMessage' should be passed by const reference."
+ },
+ "ruleId": "passedByValue"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/include\/internal\/Type1.h"
+ },
+ "region": {
+ "endColumn": 24,
+ "endLine": 163,
+ "startColumn": 24,
+ "startLine": 163
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Function parameter 'failureMessage' should be passed by const reference."
+ },
+ "ruleId": "passedByValue"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type1.cpp"
+ },
+ "region": {
+ "endColumn": 20,
+ "endLine": 92,
+ "startColumn": 20,
+ "startLine": 92
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Function parameter 'failureMessage' should be passed by const reference."
+ },
+ "ruleId": "passedByValue"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type1.cpp"
+ },
+ "region": {
+ "endColumn": 20,
+ "endLine": 107,
+ "startColumn": 20,
+ "startLine": 107
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Function parameter 'failureMessage' should be passed by const reference."
+ },
+ "ruleId": "passedByValue"
+ }
+ ],
+ "tool": {
+ "driver": {
+ "informationUri": "https:\/\/cppcheck.sourceforge.io",
+ "name": "Cppcheck",
+ "rules": [
+ {
+ "fullDescription": {
+ "text": "Parameter 'failureMessage' is passed by value. It could be passed as a const reference which is usually faster and recommended in C++."
+ },
+ "help": {
+ "text": "Parameter 'failureMessage' is passed by value. It could be passed as a const reference which is usually faster and recommended in C++."
+ },
+ "id": "passedByValue",
+ "properties": {
+ "precision": "high"
+ },
+ "shortDescription": {
+ "text": "Function parameter 'failureMessage' should be passed by const reference."
+ }
+ }
+ ],
+ "semanticVersion": "2.18"
+ }
+ }
+ }
+ ],
+ "version": "2.1.0"
+}
+
diff --git a/test/data/sarif/join/cppcheck__mnt_c_code_project_src_Type2.cpp.sarif b/test/data/sarif/join/cppcheck__mnt_c_code_project_src_Type2.cpp.sarif
new file mode 100644
index 0000000..684f828
--- /dev/null
+++ b/test/data/sarif/join/cppcheck__mnt_c_code_project_src_Type2.cpp.sarif
@@ -0,0 +1,18 @@
+{
+ "$schema": "https:\/\/docs.oasis-open.org\/sarif\/sarif\/v2.1.0\/errata01\/os\/schemas\/sarif-schema-2.1.0.json",
+ "runs": [
+ {
+ "results": [],
+ "tool": {
+ "driver": {
+ "informationUri": "https:\/\/cppcheck.sourceforge.io",
+ "name": "Cppcheck",
+ "rules": [],
+ "semanticVersion": "2.18"
+ }
+ }
+ }
+ ],
+ "version": "2.1.0"
+}
+
diff --git a/test/data/sarif/join/cppcheck__mnt_c_code_project_src_Type3.cpp.sarif b/test/data/sarif/join/cppcheck__mnt_c_code_project_src_Type3.cpp.sarif
new file mode 100644
index 0000000..90274d4
--- /dev/null
+++ b/test/data/sarif/join/cppcheck__mnt_c_code_project_src_Type3.cpp.sarif
@@ -0,0 +1,649 @@
+{
+ "$schema": "https:\/\/docs.oasis-open.org\/sarif\/sarif\/v2.1.0\/errata01\/os\/schemas\/sarif-schema-2.1.0.json",
+ "runs": [
+ {
+ "results": [
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type3.cpp"
+ },
+ "region": {
+ "endColumn": 20,
+ "endLine": 461,
+ "startColumn": 20,
+ "startLine": 461
+ }
+ }
+ },
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/include\/internal\/Type3.h"
+ },
+ "region": {
+ "endColumn": 14,
+ "endLine": 199,
+ "startColumn": 14,
+ "startLine": 199
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Technically the member function 'project::Type3::fn1' can be static (but you may consider moving to unnamed namespace)."
+ },
+ "ruleId": "functionStatic"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type3.cpp"
+ },
+ "region": {
+ "endColumn": 20,
+ "endLine": 467,
+ "startColumn": 20,
+ "startLine": 467
+ }
+ }
+ },
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/include\/internal\/Type3.h"
+ },
+ "region": {
+ "endColumn": 14,
+ "endLine": 207,
+ "startColumn": 14,
+ "startLine": 207
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Technically the member function 'project::Type3::fn2' can be static (but you may consider moving to unnamed namespace)."
+ },
+ "ruleId": "functionStatic"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type3.cpp"
+ },
+ "region": {
+ "endColumn": 20,
+ "endLine": 473,
+ "startColumn": 20,
+ "startLine": 473
+ }
+ }
+ },
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/include\/internal\/Type3.h"
+ },
+ "region": {
+ "endColumn": 14,
+ "endLine": 215,
+ "startColumn": 14,
+ "startLine": 215
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Technically the member function 'project::Type3::fn3' can be static (but you may consider moving to unnamed namespace)."
+ },
+ "ruleId": "functionStatic"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type3.cpp"
+ },
+ "region": {
+ "endColumn": 20,
+ "endLine": 292,
+ "startColumn": 20,
+ "startLine": 292
+ }
+ }
+ },
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/include\/internal\/Type3.h"
+ },
+ "region": {
+ "endColumn": 14,
+ "endLine": 292,
+ "startColumn": 14,
+ "startLine": 292
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Technically the member function 'project::Type3::fn4' can be const."
+ },
+ "ruleId": "functionConst"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type3.cpp"
+ },
+ "region": {
+ "endColumn": 15,
+ "endLine": 171,
+ "startColumn": 15,
+ "startLine": 171
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Exception thrown in function declared not to throw exceptions."
+ },
+ "ruleId": "throwInNoexceptFunction"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type3.cpp"
+ },
+ "region": {
+ "endColumn": 15,
+ "endLine": 262,
+ "startColumn": 15,
+ "startLine": 262
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Exception thrown in function declared not to throw exceptions."
+ },
+ "ruleId": "throwInNoexceptFunction"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/include\/internal\/Type3.h"
+ },
+ "region": {
+ "endColumn": 27,
+ "endLine": 132,
+ "startColumn": 27,
+ "startLine": 132
+ }
+ }
+ },
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type3.cpp"
+ },
+ "region": {
+ "endColumn": 39,
+ "endLine": 114,
+ "startColumn": 39,
+ "startLine": 114
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Function 'Type3' argument 1 names different: declaration 'arg1decl' definition 'arg1def'."
+ },
+ "ruleId": "funcArgNamesDifferent"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/include\/internal\/Type3.h"
+ },
+ "region": {
+ "endColumn": 30,
+ "endLine": 133,
+ "startColumn": 30,
+ "startLine": 133
+ }
+ }
+ },
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type3.cpp"
+ },
+ "region": {
+ "endColumn": 42,
+ "endLine": 115,
+ "startColumn": 42,
+ "startLine": 115
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Function 'Type3' argument 2 names different: declaration 'arg2decl' definition 'arg2def'."
+ },
+ "ruleId": "funcArgNamesDifferent"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/include\/internal\/Type3.h"
+ },
+ "region": {
+ "endColumn": 18,
+ "endLine": 134,
+ "startColumn": 18,
+ "startLine": 134
+ }
+ }
+ },
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type3.cpp"
+ },
+ "region": {
+ "endColumn": 30,
+ "endLine": 116,
+ "startColumn": 30,
+ "startLine": 116
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Function 'Type3' argument 3 names different: declaration 'arg3decl' definition 'arg3def'."
+ },
+ "ruleId": "funcArgNamesDifferent"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/include\/internal\/Type3.h"
+ },
+ "region": {
+ "endColumn": 37,
+ "endLine": 299,
+ "startColumn": 37,
+ "startLine": 299
+ }
+ }
+ },
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type3.cpp"
+ },
+ "region": {
+ "endColumn": 43,
+ "endLine": 304,
+ "startColumn": 43,
+ "startLine": 304
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Function 'fn5' argument 1 names different: declaration 'fn5arg1decl' definition 'fn5arg1def'."
+ },
+ "ruleId": "funcArgNamesDifferent"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/include\/internal\/Type3.h"
+ },
+ "region": {
+ "endColumn": 36,
+ "endLine": 180,
+ "startColumn": 36,
+ "startLine": 180
+ }
+ }
+ },
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type3.cpp"
+ },
+ "region": {
+ "endColumn": 42,
+ "endLine": 371,
+ "startColumn": 42,
+ "startLine": 371
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Function 'fn6' argument 1 names different: declaration 'fn6arg1decl' definition 'fn6arg1def'."
+ },
+ "ruleId": "funcArgNamesDifferent"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type3.cpp"
+ },
+ "region": {
+ "endColumn": 40,
+ "endLine": 479,
+ "startColumn": 40,
+ "startLine": 479
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Function parameter 'fnArg1NonConst' should be passed by const reference."
+ },
+ "ruleId": "passedByValue"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type3.cpp"
+ },
+ "region": {
+ "endColumn": 37,
+ "endLine": 484,
+ "startColumn": 37,
+ "startLine": 484
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Function parameter 'fnArg1NonConst' should be passed by const reference."
+ },
+ "ruleId": "passedByValue"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type3.cpp"
+ },
+ "region": {
+ "endColumn": 40,
+ "endLine": 489,
+ "startColumn": 40,
+ "startLine": 489
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Function parameter 'fnArg1NonConst' should be passed by const reference."
+ },
+ "ruleId": "passedByValue"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type3.cpp"
+ },
+ "region": {
+ "endColumn": 40,
+ "endLine": 494,
+ "startColumn": 40,
+ "startLine": 494
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Function parameter 'fnArg2NonConst' should be passed by const reference."
+ },
+ "ruleId": "passedByValue"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type3.cpp"
+ },
+ "region": {
+ "endColumn": 55,
+ "endLine": 494,
+ "startColumn": 55,
+ "startLine": 494
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Function parameter 'fnArg1NonConst' should be passed by const reference."
+ },
+ "ruleId": "passedByValue"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type3.cpp"
+ },
+ "region": {
+ "endColumn": 35,
+ "endLine": 145,
+ "startColumn": 35,
+ "startLine": 145
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Parameter 'fnArg3NonConst' can be declared as reference to const"
+ },
+ "ruleId": "constParameterReference"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type3.cpp"
+ },
+ "region": {
+ "endColumn": 46,
+ "endLine": 234,
+ "startColumn": 46,
+ "startLine": 234
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Parameter 'rhs' can be declared as reference to const"
+ },
+ "ruleId": "constParameterReference"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type3.cpp"
+ },
+ "region": {
+ "endColumn": 53,
+ "endLine": 292,
+ "startColumn": 53,
+ "startLine": 292
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Parameter 'fn5arg1def' can be declared as reference to const"
+ },
+ "ruleId": "constParameterReference"
+ }
+ ],
+ "tool": {
+ "driver": {
+ "informationUri": "https:\/\/cppcheck.sourceforge.io",
+ "name": "Cppcheck",
+ "rules": [
+ {
+ "fullDescription": {
+ "text": "The member function 'project::Type3::fn1' can be made a static function. Making a function static can bring a performance benefit since no 'this' instance is passed to the function. This change should not cause compiler errors but it does not necessarily make sense conceptually. Think about your design and the task of the function first - is it a function that must not access members of class instances? And maybe it is more appropriate to move this function to an unnamed namespace."
+ },
+ "help": {
+ "text": "The member function 'project::Type3::fn1' can be made a static function. Making a function static can bring a performance benefit since no 'this' instance is passed to the function. This change should not cause compiler errors but it does not necessarily make sense conceptually. Think about your design and the task of the function first - is it a function that must not access members of class instances? And maybe it is more appropriate to move this function to an unnamed namespace."
+ },
+ "id": "functionStatic",
+ "properties": {
+ "precision": "medium"
+ },
+ "shortDescription": {
+ "text": "Technically the member function 'project::Type3::fn1' can be static (but you may consider moving to unnamed namespace)."
+ }
+ },
+ {
+ "fullDescription": {
+ "text": "The member function 'project::Type3::fn4' can be made a const function. Making this function 'const' should not cause compiler errors. Even though the function can be made const function technically it may not make sense conceptually. Think about your design and the task of the function first - is it a function that must not change object internal state?"
+ },
+ "help": {
+ "text": "The member function 'project::Type3::fn4' can be made a const function. Making this function 'const' should not cause compiler errors. Even though the function can be made const function technically it may not make sense conceptually. Think about your design and the task of the function first - is it a function that must not change object internal state?"
+ },
+ "id": "functionConst",
+ "properties": {
+ "precision": "medium"
+ },
+ "shortDescription": {
+ "text": "Technically the member function 'project::Type3::fn4' can be const."
+ }
+ },
+ {
+ "fullDescription": {
+ "text": "Exception thrown in function declared not to throw exceptions."
+ },
+ "help": {
+ "text": "Exception thrown in function declared not to throw exceptions."
+ },
+ "id": "throwInNoexceptFunction",
+ "properties": {
+ "precision": "high",
+ "security-severity": 9.9000000000000004,
+ "tags": [
+ "security"
+ ]
+ },
+ "shortDescription": {
+ "text": "Exception thrown in function declared not to throw exceptions."
+ }
+ },
+ {
+ "fullDescription": {
+ "text": "Function 'Type3' argument 1 names different: declaration 'arg1decl' definition 'arg1def'."
+ },
+ "help": {
+ "text": "Function 'Type3' argument 1 names different: declaration 'arg1decl' definition 'arg1def'."
+ },
+ "id": "funcArgNamesDifferent",
+ "properties": {
+ "precision": "medium"
+ },
+ "shortDescription": {
+ "text": "Function 'Type3' argument 1 names different: declaration 'arg1decl' definition 'arg1def'."
+ }
+ },
+ {
+ "fullDescription": {
+ "text": "Parameter 'fnArg1NonConst' is passed by value. It could be passed as a const reference which is usually faster and recommended in C++."
+ },
+ "help": {
+ "text": "Parameter 'fnArg1NonConst' is passed by value. It could be passed as a const reference which is usually faster and recommended in C++."
+ },
+ "id": "passedByValue",
+ "properties": {
+ "precision": "high"
+ },
+ "shortDescription": {
+ "text": "Function parameter 'fnArg1NonConst' should be passed by const reference."
+ }
+ },
+ {
+ "fullDescription": {
+ "text": "Parameter 'fnArg3NonConst' can be declared as reference to const"
+ },
+ "help": {
+ "text": "Parameter 'fnArg3NonConst' can be declared as reference to const"
+ },
+ "id": "constParameterReference",
+ "properties": {
+ "precision": "high"
+ },
+ "shortDescription": {
+ "text": "Parameter 'fnArg3NonConst' can be declared as reference to const"
+ }
+ }
+ ],
+ "semanticVersion": "2.18"
+ }
+ }
+ }
+ ],
+ "version": "2.1.0"
+}
+
diff --git a/test/data/sarif/join/cppcheck__mnt_c_code_project_src_Type4.cpp.sarif b/test/data/sarif/join/cppcheck__mnt_c_code_project_src_Type4.cpp.sarif
new file mode 100644
index 0000000..70d1a7f
--- /dev/null
+++ b/test/data/sarif/join/cppcheck__mnt_c_code_project_src_Type4.cpp.sarif
@@ -0,0 +1,210 @@
+{
+ "$schema": "https:\/\/docs.oasis-open.org\/sarif\/sarif\/v2.1.0\/errata01\/os\/schemas\/sarif-schema-2.1.0.json",
+ "runs": [
+ {
+ "results": [
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/include\/internal\/Type4.h"
+ },
+ "region": {
+ "endColumn": 9,
+ "endLine": 80,
+ "startColumn": 9,
+ "startLine": 80
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Member variable 'Type4::member1' is not initialized in the constructor."
+ },
+ "ruleId": "uninitMemberVar"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/include\/internal\/Type4.h"
+ },
+ "region": {
+ "endColumn": 9,
+ "endLine": 80,
+ "startColumn": 9,
+ "startLine": 80
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Member variable 'Type4::member2' is not initialized in the constructor."
+ },
+ "ruleId": "uninitMemberVar"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/include\/internal\/Type4.h"
+ },
+ "region": {
+ "endColumn": 9,
+ "endLine": 80,
+ "startColumn": 9,
+ "startLine": 80
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Member variable 'Type4::member3' is not initialized in the constructor."
+ },
+ "ruleId": "uninitMemberVar"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/include\/internal\/Type4.h"
+ },
+ "region": {
+ "endColumn": 9,
+ "endLine": 80,
+ "startColumn": 9,
+ "startLine": 80
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Member variable 'Type4::member4' is not initialized in the constructor."
+ },
+ "ruleId": "uninitMemberVar"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/include\/internal\/Type4.h"
+ },
+ "region": {
+ "endColumn": 16,
+ "endLine": 117,
+ "startColumn": 16,
+ "startLine": 117
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Technically the member function 'project::Type4::fn1' can be static (but you may consider moving to unnamed namespace)."
+ },
+ "ruleId": "functionStatic"
+ },
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type4.cpp"
+ },
+ "region": {
+ "endColumn": 25,
+ "endLine": 73,
+ "startColumn": 25,
+ "startLine": 73
+ }
+ }
+ },
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/include\/internal\/Type4.h"
+ },
+ "region": {
+ "endColumn": 18,
+ "endLine": 153,
+ "startColumn": 18,
+ "startLine": 153
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Technically the member function 'project::Type4::fn2' can be const."
+ },
+ "ruleId": "functionConst"
+ }
+ ],
+ "tool": {
+ "driver": {
+ "informationUri": "https:\/\/cppcheck.sourceforge.io",
+ "name": "Cppcheck",
+ "rules": [
+ {
+ "fullDescription": {
+ "text": "Member variable 'Type4::member1' is not initialized in the constructor. Member variables of native types, pointers, or references are left uninitialized when the class is instantiated. That may cause bugs or undefined behavior."
+ },
+ "help": {
+ "text": "Member variable 'Type4::member1' is not initialized in the constructor. Member variables of native types, pointers, or references are left uninitialized when the class is instantiated. That may cause bugs or undefined behavior."
+ },
+ "id": "uninitMemberVar",
+ "properties": {
+ "precision": "high"
+ },
+ "shortDescription": {
+ "text": "Member variable 'Type4::member1' is not initialized in the constructor."
+ }
+ },
+ {
+ "fullDescription": {
+ "text": "The member function 'project::Type4::fn1' can be made a static function. Making a function static can bring a performance benefit since no 'this' instance is passed to the function. This change should not cause compiler errors but it does not necessarily make sense conceptually. Think about your design and the task of the function first - is it a function that must not access members of class instances? And maybe it is more appropriate to move this function to an unnamed namespace."
+ },
+ "help": {
+ "text": "The member function 'project::Type4::fn1' can be made a static function. Making a function static can bring a performance benefit since no 'this' instance is passed to the function. This change should not cause compiler errors but it does not necessarily make sense conceptually. Think about your design and the task of the function first - is it a function that must not access members of class instances? And maybe it is more appropriate to move this function to an unnamed namespace."
+ },
+ "id": "functionStatic",
+ "properties": {
+ "precision": "medium"
+ },
+ "shortDescription": {
+ "text": "Technically the member function 'project::Type4::fn1' can be static (but you may consider moving to unnamed namespace)."
+ }
+ },
+ {
+ "fullDescription": {
+ "text": "The member function 'project::Type4::fn2' can be made a const function. Making this function 'const' should not cause compiler errors. Even though the function can be made const function technically it may not make sense conceptually. Think about your design and the task of the function first - is it a function that must not change object internal state?"
+ },
+ "help": {
+ "text": "The member function 'project::Type4::fn2' can be made a const function. Making this function 'const' should not cause compiler errors. Even though the function can be made const function technically it may not make sense conceptually. Think about your design and the task of the function first - is it a function that must not change object internal state?"
+ },
+ "id": "functionConst",
+ "properties": {
+ "precision": "medium"
+ },
+ "shortDescription": {
+ "text": "Technically the member function 'project::Type4::fn2' can be const."
+ }
+ }
+ ],
+ "semanticVersion": "2.18"
+ }
+ }
+ }
+ ],
+ "version": "2.1.0"
+}
+
diff --git a/test/data/sarif/join/cppcheck__mnt_c_code_project_src_Type5.cpp.sarif b/test/data/sarif/join/cppcheck__mnt_c_code_project_src_Type5.cpp.sarif
new file mode 100644
index 0000000..317086f
--- /dev/null
+++ b/test/data/sarif/join/cppcheck__mnt_c_code_project_src_Type5.cpp.sarif
@@ -0,0 +1,70 @@
+{
+ "$schema": "https:\/\/docs.oasis-open.org\/sarif\/sarif\/v2.1.0\/errata01\/os\/schemas\/sarif-schema-2.1.0.json",
+ "runs": [
+ {
+ "results": [
+ {
+ "level": "warning",
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/src\/Type5.cpp"
+ },
+ "region": {
+ "endColumn": 32,
+ "endLine": 59,
+ "startColumn": 32,
+ "startLine": 59
+ }
+ }
+ },
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "\/mnt\/c\/code\/project\/include\/internal\/Type5.h"
+ },
+ "region": {
+ "endColumn": 23,
+ "endLine": 81,
+ "startColumn": 23,
+ "startLine": 81
+ }
+ }
+ }
+ ],
+ "message": {
+ "text": "Technically the member function 'project::Type6::fn1' can be const."
+ },
+ "ruleId": "functionConst"
+ }
+ ],
+ "tool": {
+ "driver": {
+ "informationUri": "https:\/\/cppcheck.sourceforge.io",
+ "name": "Cppcheck",
+ "rules": [
+ {
+ "fullDescription": {
+ "text": "The member function 'project::Type6::fn1' can be made a const function. Making this function 'const' should not cause compiler errors. Even though the function can be made const function technically it may not make sense conceptually. Think about your design and the task of the function first - is it a function that must not change object internal state?"
+ },
+ "help": {
+ "text": "The member function 'project::Type6::fn1' can be made a const function. Making this function 'const' should not cause compiler errors. Even though the function can be made const function technically it may not make sense conceptually. Think about your design and the task of the function first - is it a function that must not change object internal state?"
+ },
+ "id": "functionConst",
+ "properties": {
+ "precision": "medium"
+ },
+ "shortDescription": {
+ "text": "Technically the member function 'project::Type6::fn1' can be const."
+ }
+ }
+ ],
+ "semanticVersion": "2.18"
+ }
+ }
+ }
+ ],
+ "version": "2.1.0"
+}
+
diff --git a/test/test_get_files_to_check.py b/test/test_get_files_to_check.py
new file mode 100644
index 0000000..17a626c
--- /dev/null
+++ b/test/test_get_files_to_check.py
@@ -0,0 +1,84 @@
+import os
+import sys
+import unittest
+
+try:
+ PROJECT_PATH = f"{os.sep}".join(os.path.abspath(__file__).split(os.sep)[:-2])
+ sys.path.append(PROJECT_PATH)
+
+except Exception as exception:
+ print(f"Can not add project path to system path! Exiting!\nERROR: {exception}")
+ raise SystemExit(1) from exception
+
+from src import get_files_to_check as gftc
+from utils import helper_functions as util
+
+class TestGetFilesToCheck(unittest.TestCase):
+ """Unit tests for get_files_to_check module"""
+
+ def test_get_files_to_check(self):
+ """
+ Test the `get_files_to_check()` function.
+
+ This test case checks whether the `get_files_to_check()` function correctly generates a
+ list of file paths to check for static analysis issues in a given directory, excluding
+ any directories that should be skipped.
+
+ The test case creates a mock directory structure and a set of directories to skip,
+ and expects the generated list of file paths to match a pre-defined expected list of
+ file paths.
+ """
+
+ pwd = os.path.dirname(os.path.realpath(__file__))
+
+ # Excludes == None
+ expected = [
+ f"{pwd}{os.sep}utils{os.sep}dummy_project{os.sep}DummyFile.cpp",
+ f"{pwd}{os.sep}utils{os.sep}dummy_project{os.sep}DummyFile.hpp",
+ f"{pwd}{os.sep}utils{os.sep}dummy_project{os.sep}exclude_dir_1{os.sep}ExcludedFile1.hpp",
+ f"{pwd}{os.sep}utils{os.sep}dummy_project{os.sep}exclude_dir_2{os.sep}ExcludedFile2.hpp",
+ ]
+ result = gftc.get_files_to_check(
+ f"{pwd}{os.sep}utils{os.sep}dummy_project", None, "", "c++"
+ )
+
+ self.assertEqual(util.to_list_and_sort(result), expected)
+
+ # Single exclude_dir
+ expected = [
+ f"{pwd}{os.sep}utils{os.sep}dummy_project{os.sep}DummyFile.cpp",
+ f"{pwd}{os.sep}utils{os.sep}dummy_project{os.sep}DummyFile.hpp",
+ f"{pwd}{os.sep}utils{os.sep}dummy_project{os.sep}exclude_dir_2{os.sep}ExcludedFile2.hpp",
+ ]
+ result = gftc.get_files_to_check(
+ f"{pwd}{os.sep}utils{os.sep}dummy_project",
+ f"{pwd}{os.sep}utils{os.sep}dummy_project{os.sep}exclude_dir_1",
+ "",
+ "c++",
+ )
+
+ self.assertEqual(util.to_list_and_sort(result), expected)
+
+ # Multiple exclude_dir
+ expected = [
+ f"{pwd}{os.sep}utils{os.sep}dummy_project{os.sep}DummyFile.cpp",
+ f"{pwd}{os.sep}utils{os.sep}dummy_project{os.sep}DummyFile.hpp",
+ ]
+ result = gftc.get_files_to_check(
+ f"{pwd}{os.sep}utils{os.sep}dummy_project",
+ f"{pwd}{os.sep}utils{os.sep}dummy_project{os.sep}exclude_dir_1 {pwd}{os.sep}utils{os.sep}dummy_project{os.sep}exclude_dir_2",
+ "",
+ "c++",
+ )
+
+ # Preselected files present
+ expected = [f"{pwd}{os.sep}utils{os.sep}dummy_project{os.sep}DummyFile.cpp"]
+ result = gftc.get_files_to_check(
+ f"{pwd}{os.sep}utils{os.sep}dummy_project",
+ f"{pwd}{os.sep}utils{os.sep}dummy_project{os.sep}exclude_dir_1 {pwd}{os.sep}utils{os.sep}dummy_project{os.sep}exclude_dir_2",
+ f"{pwd}{os.sep}utils{os.sep}dummy_project{os.sep}DummyFile.cpp {pwd}{os.sep}utils{os.sep}dummy_project{os.sep}exclude_dir_1{os.sep}ExcludedFile1.hpp",
+ "c++",
+ )
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/test_join_sarif.py b/test/test_join_sarif.py
new file mode 100644
index 0000000..5bf1462
--- /dev/null
+++ b/test/test_join_sarif.py
@@ -0,0 +1,53 @@
+import filecmp
+import itertools
+import json
+import jsonpickle
+import os
+import sys
+import tempfile
+import unittest
+
+try:
+ PROJECT_PATH = f"{os.sep}".join(os.path.abspath(__file__).split(os.sep)[:-2])
+ sys.path.append(PROJECT_PATH)
+
+except Exception as exception:
+ print(f"Can not add project path to system path! Exiting!\nERROR: {exception}")
+ raise SystemExit(1) from exception
+
+from pathlib import Path
+from src import join_sarif as sarops
+from utils import helper_functions as utils
+
+class TestJoinSarif(unittest.TestCase):
+ """Unit tests for join_sarif module"""
+
+ @classmethod
+ def setUpClass(cls):
+ dir = f"{f"{os.sep}".join(os.path.abspath(__file__).split(os.sep)[:-1])}{os.sep}data{os.sep}sarif{os.sep}join"
+ cls.to_join_tjs_js, cls.to_join_tjs_gh, cls.to_join_twjs_jsw, cls.to_join_twjs_jsjd = itertools.tee(
+ Path(dir).rglob("cppcheck__mnt_c_code_project_src_Type*.cpp.sarif"),
+ 4
+ )
+
+ def test_join_sarif(self):
+ joined = sarops.join_sarif(self.to_join_tjs_js)
+ hashes = utils.genhashes(self.to_join_tjs_gh)
+
+ for run in joined.runs:
+ run_hash = hash(json.dumps(run, sort_keys=True, ensure_ascii=True, default=lambda v: repr(v) + str(hash(v))))
+ self.assertIn(run_hash, hashes)
+
+ def test_write_joined_sarif(self):
+ with tempfile.NamedTemporaryFile("w", delete_on_close=False) as written_file:
+ written_file.close()
+ sarops.write_joined_sarif(sarops.join_sarif(self.to_join_twjs_jsw), written_file.name)
+
+ with tempfile.NamedTemporaryFile("w", delete_on_close=False) as joined_file:
+ joined_file.close()
+ json_sarif = jsonpickle.encode(sarops.join_sarif(self.to_join_twjs_jsjd))
+ open(joined_file.name, "w").write(json_sarif)
+ self.assertTrue(filecmp.cmp(written_file.name, joined_file.name, False))
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/test_sa_utils.py b/test/test_sa_utils.py
new file mode 100644
index 0000000..6c4c13b
--- /dev/null
+++ b/test/test_sa_utils.py
@@ -0,0 +1,40 @@
+import unittest
+import os
+import sys
+
+try:
+ PROJECT_PATH = f"{os.sep}".join(os.path.abspath(__file__).split(os.sep)[:-2])
+ sys.path.append(PROJECT_PATH)
+except Exception as exception:
+ print(f"Can not add project path to system path! Exiting!\nERROR: {exception}")
+ raise SystemExit(1) from exception
+
+from pytest import MonkeyPatch
+
+class TestUtils(unittest.TestCase):
+ """Unit tests for utils_sa module"""
+
+ maxDiff = None
+
+ @classmethod
+ def mock_env(self, mp: MonkeyPatch):
+ mp.setenv("GITHUB_WORKSPACE", f"{PROJECT_PATH}{os.sep}test{os.sep}utils{os.sep}dummy_project")
+ mp.setenv("INPUT_VERBOSE", "True")
+ mp.setenv("INPUT_REPORT_PR_CHANGES_ONLY", "False")
+ mp.setenv("INPUT_REPO", "RepoName")
+ mp.setenv("GITHUB_SHA", "1234")
+ mp.setenv("INPUT_COMMENT_TITLE", "title")
+
+ def test_get_lines_changed_from_patch(self):
+ with MonkeyPatch.context() as mp:
+ self.mock_env(mp)
+
+ from src import sa_utils
+
+ patch = "@@ -43,6 +48,8 @@\n@@ -0,0 +1 @@"
+
+ lines = sa_utils.get_lines_changed_from_patch(patch)
+ self.assertEqual(lines, [(48, 56), (1, 1)])
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/test_static_analysis_cpp.py b/test/test_static_analysis_cpp.py
index d8974d8..483fc81 100644
--- a/test/test_static_analysis_cpp.py
+++ b/test/test_static_analysis_cpp.py
@@ -1,7 +1,6 @@
import unittest
import os
import sys
-import utils.helper_functions as utils
try:
PROJECT_PATH = f"{os.sep}".join(os.path.abspath(__file__).split(os.sep)[:-2])
@@ -10,216 +9,171 @@
print(f"Can not add project path to system path! Exiting!\nERROR: {exception}")
raise SystemExit(1) from exception
-os.environ["GITHUB_WORKSPACE"] = f"{PROJECT_PATH}/test/utils/dummy_project"
-os.environ["INPUT_VERBOSE"] = "True"
-os.environ["INPUT_REPORT_PR_CHANGES_ONLY"] = "False"
-os.environ["INPUT_REPO"] = "RepoName"
-os.environ["GITHUB_SHA"] = "1234"
-os.environ["INPUT_COMMENT_TITLE"] = "title"
+from pytest import MonkeyPatch
-from src import static_analysis_cpp, get_files_to_check
+class TestStaticAnalysisCpp(unittest.TestCase):
+ """Unit tests for static_analysis_cpp"""
-def to_list_and_sort(string_in):
- # create list (of strings) from space separated string
- # and then sort it
- list_out = string_in.split(" ")
- list_out.sort()
+ maxDiff = None
- return list_out
+ @classmethod
+ def mock_env(self, mp: MonkeyPatch):
+ mp.setenv("GITHUB_WORKSPACE", f"{PROJECT_PATH}{os.sep}test{os.sep}utils{os.sep}dummy_project")
+ mp.setenv("INPUT_VERBOSE", "True")
+ mp.setenv("INPUT_REPORT_PR_CHANGES_ONLY", "False")
+ mp.setenv("INPUT_REPO", "RepoName")
+ mp.setenv("GITHUB_SHA", "1234")
+ mp.setenv("INPUT_COMMENT_TITLE", "title")
+ def test_create_comment_for_output(self):
+ with MonkeyPatch.context() as mp:
+ self.mock_env(mp)
+
+ from src import static_analysis_cpp
+
+ cppcheck_content = [
+ f"{os.getenv("GITHUB_WORKSPACE")}{os.sep}DummyFile.cpp:8:23: style: Error message\n",
+ " Part of code\n",
+ " ^\n",
+ f"{os.getenv("GITHUB_WORKSPACE")}{os.sep}DummyFile.cpp:6:12: note: Note message\n",
+ " Part of code\n",
+ " ^\n",
+ f"{os.getenv("GITHUB_WORKSPACE")}{os.sep}DummyFile.cpp:7:4: note: Another note message\n",
+ " Part of code\n",
+ " ^\n",
+ f"{os.getenv("GITHUB_WORKSPACE")}{os.sep}DummyFile.cpp:3:0: style: Error message\n",
+ " Part of code\n",
+ " ^\n",
+ ]
+
+ files_changed_in_pr = {
+ f"{os.getenv("GITHUB_WORKSPACE")}{os.sep}DummyFile.hpp": ("added", (1, 10)),
+ f"{os.getenv("GITHUB_WORKSPACE")}{os.sep}DummyFile.cpp": ("added", (1, 10)),
+ }
+ result = static_analysis_cpp.create_comment_for_output(
+ cppcheck_content, os.getenv("GITHUB_WORKSPACE"), files_changed_in_pr, False
+ )
+
+ sha = os.getenv("GITHUB_SHA")
+ repo_name = os.getenv("INPUT_REPO")
+ expected = (
+ f"\n\nhttps://github.com/{repo_name}/blob/{sha}/DummyFile.cpp#L8-L9 \n"
+ f"```diff\n!Line: 8 - style: Error message"
+ f"\n\n!Line: 6 - note: Note message"
+ f"\n!Line: 7 - note: Another note message\n``` "
+ f"\n\n\n\nhttps://github.com/{repo_name}/blob/{sha}/DummyFile.cpp#L3-L8 \n"
+ f"```diff\n!Line: 3 - style: Error message\n\n``` \n
\n"
+ )
+
+ print(result)
+
+ self.assertEqual(result, (expected, 2))
+
+ def test_prepare_comment_body_empty(self):
+ with MonkeyPatch.context() as mp:
+ self.mock_env(mp)
+
+ from src import static_analysis_cpp
+ from utils import helper_functions as utils
+
+ comment_title = os.getenv("INPUT_COMMENT_TITLE")
+ comment_body = static_analysis_cpp.prepare_comment_body("", "", "", "", 0, 0, 0, 0)
+
+ # Empty results
+ expected_comment_body = utils.generate_comment(comment_title, "", 0, "cppcheck")
+
+ self.assertEqual(expected_comment_body, comment_body)
+
+ def test_prepare_comment_body_single_cppcheck(self):
+ with MonkeyPatch.context() as mp:
+ self.mock_env(mp)
+
+ from src import static_analysis_cpp
+ from utils import helper_functions as utils
+
+ comment_title = os.getenv("INPUT_COMMENT_TITLE")
+
+ cppcheck_issues_found = 1
+ cppcheck_comment = "dummy issue"
+ expected_comment_body = utils.generate_comment(
+ comment_title, cppcheck_comment, cppcheck_issues_found,
+ "cppcheck"
+ )
+
+ comment_body = static_analysis_cpp.prepare_comment_body(
+ "", cppcheck_comment, "", "",
+ 0, cppcheck_issues_found, 0, 0
+ )
+
+ self.assertEqual(expected_comment_body, comment_body)
+
+ def test_prepare_comment_body_multi_cppcheck(self):
+ with MonkeyPatch.context() as mp:
+ self.mock_env(mp)
+
+ from src import static_analysis_cpp
+ from utils import helper_functions as utils
+
+ comment_title = os.getenv("INPUT_COMMENT_TITLE")
+
+ cppcheck_issues_found = 4
+ cppcheck_comment = "dummy issues"
+ expected_comment_body = utils.generate_comment(
+ comment_title, cppcheck_comment, cppcheck_issues_found,
+ "cppcheck"
+ )
+
+ comment_body = static_analysis_cpp.prepare_comment_body(
+ "", cppcheck_comment, "", "",
+ 0, cppcheck_issues_found, 0, 0
+ )
+
+ self.assertEqual(expected_comment_body, comment_body)
+
+ def test_prepare_comment_body_single_clang_tidy(self):
+ with MonkeyPatch.context() as mp:
+ self.mock_env(mp)
+
+ from src import static_analysis_cpp
+ from utils import helper_functions as utils
+
+ comment_title = os.getenv("INPUT_COMMENT_TITLE")
-class TestStaticAnalysisCpp(unittest.TestCase):
- """Unit tests for static_analysis_cpp"""
+ clang_tidy_issues_found = 1
+ clang_tidy_comment = "dummy issue"
+ expected_comment_body = utils.generate_comment(
+ comment_title, clang_tidy_comment, clang_tidy_issues_found, "clang-tidy"
+ )
- maxDiff = None
+ comment_body = static_analysis_cpp.prepare_comment_body(
+ "", "", "", clang_tidy_comment,
+ 0, 0, 0, clang_tidy_issues_found
+ )
- def test_create_comment_for_output(self):
- """
- Test the `create_comment_for_output()` function.
-
- This test case checks whether the `create_comment_for_output()` function correctly
- generates a GitHub comment that displays static analysis issues for a given set of
- files.
-
- The test case creates a mock set of files and static analysis issues, and expects the
- generated GitHub comment to match a pre-defined expected string.
- """
-
- cppcheck_content = [
- "/github/workspace/DummyFile.cpp:8:23: style: Error message\n",
- " Part of code\n",
- " ^\n",
- "/github/workspace/DummyFile.cpp:6:12: note: Note message\n",
- " Part of code\n",
- " ^\n",
- "/github/workspace/DummyFile.cpp:7:4: note: Another note message\n",
- " Part of code\n",
- " ^\n",
- "/github/workspace/DummyFile.cpp:3:0: style: Error message\n",
- " Part of code\n",
- " ^\n",
- ]
-
- files_changed_in_pr = {
- "/github/workspace/DummyFile.hpp": ("added", (1, 10)),
- "/github/workspace/DummyFile.cpp": ("added", (1, 10)),
- }
- result = static_analysis_cpp.create_comment_for_output(
- cppcheck_content, "/github/workspace", files_changed_in_pr, False
- )
-
- sha = os.getenv("GITHUB_SHA")
- repo_name = os.getenv("INPUT_REPO")
- expected = (
- f"\n\nhttps://github.com/{repo_name}/blob/{sha}/DummyFile.cpp#L8-L9 \n"
- f"```diff\n!Line: 8 - style: Error message"
- f"\n\n!Line: 6 - note: Note message"
- f"\n!Line: 7 - note: Another note message\n``` "
- f"\n\n\n\nhttps://github.com/{repo_name}/blob/{sha}/DummyFile.cpp#L3-L8 \n"
- f"```diff\n!Line: 3 - style: Error message\n\n``` \n
\n"
- )
-
- print(result)
-
- self.assertEqual(result, (expected, 2))
-
- def test_prepare_comment_body(self):
- """
- Test the `prepare_comment_body()` function.
-
- This test case checks whether the `prepare_comment_body()` function correctly generates
- the body text of a GitHub comment for a given set of static analysis issues.
-
- The test case creates mock input parameters representing different types of static
- analysis issues, and expects the generated comment body to match a pre-defined expected
- string.
- """
-
- comment_title = os.getenv("INPUT_COMMENT_TITLE")
- comment_body = static_analysis_cpp.prepare_comment_body("", "", 0, 0)
-
- # Empty results
- expected_comment_body = utils.generate_comment(comment_title, "", 0, "cppcheck")
-
- self.assertEqual(expected_comment_body, comment_body)
-
- # Multiple cppcheck issues
- cppcheck_issues_found = 4
- cppcheck_comment = "dummy issues"
- expected_comment_body = utils.generate_comment(
- comment_title, cppcheck_comment, cppcheck_issues_found, "cppcheck"
- )
-
- comment_body = static_analysis_cpp.prepare_comment_body(
- cppcheck_comment, "", cppcheck_issues_found, 0
- )
-
- self.assertEqual(expected_comment_body, comment_body)
-
- # Single cppcheck issue
- cppcheck_issues_found = 1
- cppcheck_comment = "dummy issue"
- expected_comment_body = utils.generate_comment(
- comment_title, cppcheck_comment, cppcheck_issues_found, "cppcheck"
- )
-
- comment_body = static_analysis_cpp.prepare_comment_body(
- cppcheck_comment, "", cppcheck_issues_found, 0
- )
-
- self.assertEqual(expected_comment_body, comment_body)
-
- # Multiple clang-tidy issues
- clang_tidy_issues_found = 4
- clang_tidy_comment = "dummy issues"
- expected_comment_body = utils.generate_comment(
- comment_title, clang_tidy_comment, clang_tidy_issues_found, "clang-tidy"
- )
-
- comment_body = static_analysis_cpp.prepare_comment_body(
- "", clang_tidy_comment, 0, clang_tidy_issues_found
- )
-
- self.assertEqual(expected_comment_body, comment_body)
-
- # Single clang-tidy issue
- clang_tidy_issues_found = 1
- clang_tidy_comment = "dummy issue"
- expected_comment_body = utils.generate_comment(
- comment_title, clang_tidy_comment, clang_tidy_issues_found, "clang-tidy"
- )
-
- comment_body = static_analysis_cpp.prepare_comment_body(
- "", clang_tidy_comment, 0, clang_tidy_issues_found
- )
-
- self.assertEqual(expected_comment_body, comment_body)
-
- def test_get_files_to_check(self):
- """
- Test the `get_files_to_check()` function.
-
- This test case checks whether the `get_files_to_check()` function correctly generates a
- list of file paths to check for static analysis issues in a given directory, excluding
- any directories that should be skipped.
-
- The test case creates a mock directory structure and a set of directories to skip,
- and expects the generated list of file paths to match a pre-defined expected list of
- file paths.
- """
-
- pwd = os.path.dirname(os.path.realpath(__file__))
-
- # Excludes == None
- expected = [
- f"{pwd}/utils/dummy_project/DummyFile.cpp",
- f"{pwd}/utils/dummy_project/DummyFile.hpp",
- f"{pwd}/utils/dummy_project/exclude_dir_1/ExcludedFile1.hpp",
- f"{pwd}/utils/dummy_project/exclude_dir_2/ExcludedFile2.hpp",
- ]
- result = get_files_to_check.get_files_to_check(
- f"{pwd}/utils/dummy_project", None, "", "c++"
- )
-
- self.assertEqual(to_list_and_sort(result), expected)
-
- # Single exclude_dir
- expected = [
- f"{pwd}/utils/dummy_project/DummyFile.cpp",
- f"{pwd}/utils/dummy_project/DummyFile.hpp",
- f"{pwd}/utils/dummy_project/exclude_dir_2/ExcludedFile2.hpp",
- ]
- result = get_files_to_check.get_files_to_check(
- f"{pwd}/utils/dummy_project",
- f"{pwd}/utils/dummy_project/exclude_dir_1",
- "",
- "c++",
- )
-
- self.assertEqual(to_list_and_sort(result), expected)
-
- # Multiple exclude_dir
- expected = [
- f"{pwd}/utils/dummy_project/DummyFile.cpp",
- f"{pwd}/utils/dummy_project/DummyFile.hpp",
- ]
- result = get_files_to_check.get_files_to_check(
- f"{pwd}/utils/dummy_project",
- f"{pwd}/utils/dummy_project/exclude_dir_1 {pwd}/utils/dummy_project/exclude_dir_2",
- "",
- "c++",
- )
-
- # Preselected files present
- expected = [f"{pwd}/utils/dummy_project/DummyFile.cpp"]
- result = get_files_to_check.get_files_to_check(
- f"{pwd}/utils/dummy_project",
- f"{pwd}/utils/dummy_project/exclude_dir_1 {pwd}/utils/dummy_project/exclude_dir_2",
- f"{pwd}/utils/dummy_project/DummyFile.cpp {pwd}/utils/dummy_project/exclude_dir_1/ExcludedFile1.hpp",
- "c++",
- )
+ self.assertEqual(expected_comment_body, comment_body)
+
+ def test_prepare_comment_body_multi_clang_tidy(self):
+ with MonkeyPatch.context() as mp:
+ self.mock_env(mp)
+
+ from src import static_analysis_cpp
+ from utils import helper_functions as utils
+
+ comment_title = os.getenv("INPUT_COMMENT_TITLE")
+
+ clang_tidy_issues_found = 4
+ clang_tidy_comment = "dummy issues"
+ expected_comment_body = utils.generate_comment(
+ comment_title, clang_tidy_comment, clang_tidy_issues_found, "clang-tidy"
+ )
+
+ comment_body = static_analysis_cpp.prepare_comment_body(
+ "", "", "", clang_tidy_comment,
+ 0, 0, 0, clang_tidy_issues_found
+ )
+ self.assertEqual(expected_comment_body, comment_body)
if __name__ == "__main__":
unittest.main()
diff --git a/test/test_static_analysis_python.py b/test/test_static_analysis_python.py
index c1a8894..23840cc 100644
--- a/test/test_static_analysis_python.py
+++ b/test/test_static_analysis_python.py
@@ -10,204 +10,201 @@
print(f"Can not add project path to system path! Exiting!\nERROR: {exception}")
raise SystemExit(1) from exception
-os.environ["GITHUB_WORKSPACE"] = f"{PROJECT_PATH}/test/utils/dummy_project"
-os.environ["INPUT_VERBOSE"] = "True"
-os.environ["INPUT_REPORT_PR_CHANGES_ONLY"] = "False"
-os.environ["INPUT_REPO"] = "RepoName"
-os.environ["GITHUB_SHA"] = "1234"
-os.environ["INPUT_COMMENT_TITLE"] = "title"
-
-from src import static_analysis_python
-
+from pytest import MonkeyPatch
class TestStaticAnalysisPython(unittest.TestCase):
+
"""Unit tests for static_analysis_python"""
maxDiff = None
- def test_create_comment_for_output(self):
- """
- Test the `create_comment_for_output()` function.
-
- This test case checks whether the `create_comment_for_output()` function correctly
- generates a GitHub comment that displays static analysis issues for a given set of
- files.
-
- The test case creates a mock set of files and static analysis issues, and expects the
- generated GitHub comment to match a pre-defined expected string.
- """
-
- pylint_content = r""" [
- {
- "type": "convention",
- "module": "dummy",
- "obj": "",
- "line": 5,
- "column": 0,
- "endLine": 5,
- "endColumn": 5,
- "path": "dummy.py",
- "symbol": "invalid-name",
- "message": "Constant name \"shift\" doesn't conform to UPPER_CASE naming style",
- "message-id": "C0103"
- },
- {
- "type": "convention",
- "module": "dummy",
- "obj": "",
- "line": 8,
- "column": 0,
- "endLine": 8,
- "endColumn": 7,
- "path": "dummy.py",
- "symbol": "invalid-name",
- "message": "Constant name \"letters\" doesn't conform to UPPER_CASE naming style",
- "message-id": "C0103"
- },
- {
- "type": "convention",
- "module": "dummy",
- "obj": "",
- "line": 9,
- "column": 0,
- "endLine": 9,
- "endColumn": 7,
- "path": "dummy.py",
- "symbol": "invalid-name",
- "message": "Constant name \"encoded\" doesn't conform to UPPER_CASE naming style",
- "message-id": "C0103"
- },
- {
- "type": "convention",
- "module": "dummy",
- "obj": "",
- "line": 13,
- "column": 12,
- "endLine": 13,
- "endColumn": 19,
- "path": "dummy.py",
- "symbol": "invalid-name",
- "message": "Constant name \"encoded\" doesn't conform to UPPER_CASE naming style",
- "message-id": "C0103"
- },
- {
- "type": "convention",
- "module": "dummy",
- "obj": "",
- "line": 15,
- "column": 12,
- "endLine": 15,
- "endColumn": 13,
- "path": "dummy.py",
- "symbol": "invalid-name",
- "message": "Constant name \"x\" doesn't conform to UPPER_CASE naming style",
- "message-id": "C0103"
- },
- {
- "type": "convention",
- "module": "dummy",
- "obj": "",
- "line": 16,
- "column": 12,
- "endLine": 16,
- "endColumn": 19,
- "path": "dummy.py",
- "symbol": "invalid-name",
- "message": "Constant name \"encoded\" doesn't conform to UPPER_CASE naming style",
- "message-id": "C0103"
- },
- {
- "type": "convention",
- "module": "dummy",
- "obj": "",
- "line": 20,
- "column": 12,
- "endLine": 20,
- "endColumn": 19,
- "path": "dummy.py",
- "symbol": "invalid-name",
- "message": "Constant name \"encoded\" doesn't conform to UPPER_CASE naming style",
- "message-id": "C0103"
- },
- {
- "type": "convention",
- "module": "dummy",
- "obj": "",
- "line": 22,
- "column": 12,
- "endLine": 22,
- "endColumn": 13,
- "path": "dummy.py",
- "symbol": "invalid-name",
- "message": "Constant name \"x\" doesn't conform to UPPER_CASE naming style",
- "message-id": "C0103"
- },
- {
- "type": "convention",
- "module": "dummy",
- "obj": "",
- "line": 23,
- "column": 12,
- "endLine": 23,
- "endColumn": 19,
- "path": "dummy.py",
- "symbol": "invalid-name",
- "message": "Constant name \"encoded\" doesn't conform to UPPER_CASE naming style",
- "message-id": "C0103"
- }
- ]"""
-
- files_changed_in_pr = {"/github/workspace/dummy.py": ("added", (1, 25))}
- result = static_analysis_python.create_comment_for_output(
- json.loads(pylint_content), files_changed_in_pr, False
- )
-
- sha = os.getenv("GITHUB_SHA")
- repo_name = os.getenv("INPUT_REPO")
- expected = (
- f"\n\nhttps://github.com/{repo_name}/blob/{sha}/dummy.py#L5-L10 \n"
- "```diff"
- '\n!Line: 5 - C0103: Constant name "shift" doesn\'t conform to UPPER_CASE naming style (invalid-name)\n'
- "``` \n
"
- f"\n\n\n\nhttps://github.com/{repo_name}/blob/{sha}/dummy.py#L8-L13 \n"
- "```diff\n"
- '!Line: 8 - C0103: Constant name "letters" doesn\'t conform to UPPER_CASE naming style (invalid-name)\n'
- "``` \n
"
- f"\n\n\n\nhttps://github.com/{repo_name}/blob/{sha}/dummy.py#L9-L14 \n"
- "```diff\n"
- '!Line: 9 - C0103: Constant name "encoded" doesn\'t conform to UPPER_CASE naming style (invalid-name)\n'
- "```"
- " \n
"
- f"\n\n\n\nhttps://github.com/{repo_name}/blob/{sha}/dummy.py#L13-L18 \n"
- "```diff\n"
- '!Line: 13 - C0103: Constant name "encoded" doesn\'t conform to UPPER_CASE naming style (invalid-name)\n'
- "``` \n
"
- f"\n\n\n\nhttps://github.com/{repo_name}/blob/{sha}/dummy.py#L15-L20 \n"
- "```diff\n"
- '!Line: 15 - C0103: Constant name "x" doesn\'t conform to UPPER_CASE naming style (invalid-name)\n'
- "``` \n
"
- f"\n\n\n\nhttps://github.com/{repo_name}/blob/{sha}/dummy.py#L16-L21 \n"
- "```diff\n"
- '!Line: 16 - C0103: Constant name "encoded" doesn\'t conform to UPPER_CASE naming style (invalid-name)\n'
- "``` \n
"
- f"\n\n\n\nhttps://github.com/{repo_name}/blob/{sha}/dummy.py#L20-L25 \n"
- "```diff\n"
- '!Line: 20 - C0103: Constant name "encoded" doesn\'t conform to UPPER_CASE naming style (invalid-name)\n'
- "``` \n
"
- f"\n\n\n\nhttps://github.com/{repo_name}/blob/{sha}/dummy.py#L22-L25 \n"
- "```diff\n"
- '!Line: 22 - C0103: Constant name "x" doesn\'t conform to UPPER_CASE naming style (invalid-name)\n'
- "``` \n
"
- f"\n\n\n\nhttps://github.com/{repo_name}/blob/{sha}/dummy.py#L23-L25 \n"
- "```diff\n"
- '!Line: 23 - C0103: Constant name "encoded" doesn\'t conform to UPPER_CASE naming style (invalid-name)\n'
- "``` \n
\n"
- )
-
- print(result)
-
- self.assertEqual(result, (expected, 9))
+ @classmethod
+ def mock_env(self, mp: MonkeyPatch):
+ mp.setenv("GITHUB_WORKSPACE", f"{PROJECT_PATH}{os.sep}test{os.sep}utils{os.sep}dummy_project")
+ mp.setenv("INPUT_VERBOSE", "True")
+ mp.setenv("INPUT_REPORT_PR_CHANGES_ONLY", "False")
+ mp.setenv("INPUT_REPO", "RepoName")
+ mp.setenv("GITHUB_SHA", "1234")
+ mp.setenv("INPUT_COMMENT_TITLE", "title")
+ def test_create_comment_for_output(self):
+ with MonkeyPatch.context() as mp:
+ self.mock_env(mp)
+
+ from src import static_analysis_python
+
+ pylint_content = r""" [
+ {
+ "type": "convention",
+ "module": "dummy",
+ "obj": "",
+ "line": 5,
+ "column": 0,
+ "endLine": 5,
+ "endColumn": 5,
+ "path": "dummy.py",
+ "symbol": "invalid-name",
+ "message": "Constant name \"shift\" doesn't conform to UPPER_CASE naming style",
+ "message-id": "C0103"
+ },
+ {
+ "type": "convention",
+ "module": "dummy",
+ "obj": "",
+ "line": 8,
+ "column": 0,
+ "endLine": 8,
+ "endColumn": 7,
+ "path": "dummy.py",
+ "symbol": "invalid-name",
+ "message": "Constant name \"letters\" doesn't conform to UPPER_CASE naming style",
+ "message-id": "C0103"
+ },
+ {
+ "type": "convention",
+ "module": "dummy",
+ "obj": "",
+ "line": 9,
+ "column": 0,
+ "endLine": 9,
+ "endColumn": 7,
+ "path": "dummy.py",
+ "symbol": "invalid-name",
+ "message": "Constant name \"encoded\" doesn't conform to UPPER_CASE naming style",
+ "message-id": "C0103"
+ },
+ {
+ "type": "convention",
+ "module": "dummy",
+ "obj": "",
+ "line": 13,
+ "column": 12,
+ "endLine": 13,
+ "endColumn": 19,
+ "path": "dummy.py",
+ "symbol": "invalid-name",
+ "message": "Constant name \"encoded\" doesn't conform to UPPER_CASE naming style",
+ "message-id": "C0103"
+ },
+ {
+ "type": "convention",
+ "module": "dummy",
+ "obj": "",
+ "line": 15,
+ "column": 12,
+ "endLine": 15,
+ "endColumn": 13,
+ "path": "dummy.py",
+ "symbol": "invalid-name",
+ "message": "Constant name \"x\" doesn't conform to UPPER_CASE naming style",
+ "message-id": "C0103"
+ },
+ {
+ "type": "convention",
+ "module": "dummy",
+ "obj": "",
+ "line": 16,
+ "column": 12,
+ "endLine": 16,
+ "endColumn": 19,
+ "path": "dummy.py",
+ "symbol": "invalid-name",
+ "message": "Constant name \"encoded\" doesn't conform to UPPER_CASE naming style",
+ "message-id": "C0103"
+ },
+ {
+ "type": "convention",
+ "module": "dummy",
+ "obj": "",
+ "line": 20,
+ "column": 12,
+ "endLine": 20,
+ "endColumn": 19,
+ "path": "dummy.py",
+ "symbol": "invalid-name",
+ "message": "Constant name \"encoded\" doesn't conform to UPPER_CASE naming style",
+ "message-id": "C0103"
+ },
+ {
+ "type": "convention",
+ "module": "dummy",
+ "obj": "",
+ "line": 22,
+ "column": 12,
+ "endLine": 22,
+ "endColumn": 13,
+ "path": "dummy.py",
+ "symbol": "invalid-name",
+ "message": "Constant name \"x\" doesn't conform to UPPER_CASE naming style",
+ "message-id": "C0103"
+ },
+ {
+ "type": "convention",
+ "module": "dummy",
+ "obj": "",
+ "line": 23,
+ "column": 12,
+ "endLine": 23,
+ "endColumn": 19,
+ "path": "dummy.py",
+ "symbol": "invalid-name",
+ "message": "Constant name \"encoded\" doesn't conform to UPPER_CASE naming style",
+ "message-id": "C0103"
+ }
+ ]"""
+
+ files_changed_in_pr = {
+ f"{os.getenv("GITHUB_WORKSPACE")}{os.sep}dummy.py": ("added", (1, 25))
+ }
+ result = static_analysis_python.create_comment_for_output(
+ json.loads(pylint_content), files_changed_in_pr, False
+ )
+
+ sha = os.getenv("GITHUB_SHA")
+ repo_name = os.getenv("INPUT_REPO")
+ expected = (
+ f"\n\nhttps://github.com/{repo_name}/blob/{sha}/dummy.py#L5-L10 \n"
+ "```diff"
+ '\n!Line: 5 - C0103: Constant name "shift" doesn\'t conform to UPPER_CASE naming style (invalid-name)\n'
+ "``` \n
"
+ f"\n\n\n\nhttps://github.com/{repo_name}/blob/{sha}/dummy.py#L8-L13 \n"
+ "```diff\n"
+ '!Line: 8 - C0103: Constant name "letters" doesn\'t conform to UPPER_CASE naming style (invalid-name)\n'
+ "``` \n
"
+ f"\n\n\n\nhttps://github.com/{repo_name}/blob/{sha}/dummy.py#L9-L14 \n"
+ "```diff\n"
+ '!Line: 9 - C0103: Constant name "encoded" doesn\'t conform to UPPER_CASE naming style (invalid-name)\n'
+ "```"
+ " \n
"
+ f"\n\n\n\nhttps://github.com/{repo_name}/blob/{sha}/dummy.py#L13-L18 \n"
+ "```diff\n"
+ '!Line: 13 - C0103: Constant name "encoded" doesn\'t conform to UPPER_CASE naming style (invalid-name)\n'
+ "``` \n
"
+ f"\n\n\n\nhttps://github.com/{repo_name}/blob/{sha}/dummy.py#L15-L20 \n"
+ "```diff\n"
+ '!Line: 15 - C0103: Constant name "x" doesn\'t conform to UPPER_CASE naming style (invalid-name)\n'
+ "``` \n
"
+ f"\n\n\n\nhttps://github.com/{repo_name}/blob/{sha}/dummy.py#L16-L21 \n"
+ "```diff\n"
+ '!Line: 16 - C0103: Constant name "encoded" doesn\'t conform to UPPER_CASE naming style (invalid-name)\n'
+ "``` \n
"
+ f"\n\n\n\nhttps://github.com/{repo_name}/blob/{sha}/dummy.py#L20-L25 \n"
+ "```diff\n"
+ '!Line: 20 - C0103: Constant name "encoded" doesn\'t conform to UPPER_CASE naming style (invalid-name)\n'
+ "``` \n
"
+ f"\n\n\n\nhttps://github.com/{repo_name}/blob/{sha}/dummy.py#L22-L25 \n"
+ "```diff\n"
+ '!Line: 22 - C0103: Constant name "x" doesn\'t conform to UPPER_CASE naming style (invalid-name)\n'
+ "``` \n
"
+ f"\n\n\n\nhttps://github.com/{repo_name}/blob/{sha}/dummy.py#L23-L25 \n"
+ "```diff\n"
+ '!Line: 23 - C0103: Constant name "encoded" doesn\'t conform to UPPER_CASE naming style (invalid-name)\n'
+ "``` \n
\n"
+ )
+
+ print(result)
+
+ self.assertEqual(result, (expected, 9))
if __name__ == "__main__":
unittest.main()
diff --git a/test/test_utils.py b/test/test_utils.py
deleted file mode 100644
index adbb77c..0000000
--- a/test/test_utils.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import unittest
-import os
-import sys
-
-try:
- PROJECT_PATH = f"{os.sep}".join(os.path.abspath(__file__).split(os.sep)[:-2])
- sys.path.append(PROJECT_PATH)
-except Exception as exception:
- print(f"Can not add project path to system path! Exiting!\nERROR: {exception}")
- raise SystemExit(1) from exception
-
-from src import sa_utils
-
-
-class TestUtils(unittest.TestCase):
- """Unit tests for utils_sa module"""
-
- maxDiff = None
-
- def test_get_lines_changed_from_patch(self):
- patch = "@@ -43,6 +48,8 @@\n@@ -0,0 +1 @@"
-
- lines = sa_utils.get_lines_changed_from_patch(patch)
- self.assertEqual(lines, [(48, 56), (1, 1)])
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/test/utils/dummy_project/DummyFile.hpp b/test/utils/dummy_project/DummyFile.hpp
index 21054e8..524b57a 100644
--- a/test/utils/dummy_project/DummyFile.hpp
+++ b/test/utils/dummy_project/DummyFile.hpp
@@ -1,3 +1,3 @@
inline void func() {
int anotherUnused;
-}
\ No newline at end of file
+}
diff --git a/test/utils/helper_functions.py b/test/utils/helper_functions.py
index cee3548..ac42862 100644
--- a/test/utils/helper_functions.py
+++ b/test/utils/helper_functions.py
@@ -1,4 +1,18 @@
+import json
+
+from sarif_om import SarifLog
+
+def to_list_and_sort(string_in):
+ # create list (of strings) from space separated string
+ # and then sort it
+ list_out = string_in.split(" ")
+ list_out.sort()
+
+ return list_out
+
def generate_comment(comment_title, content, issues_found, tool_name):
+ SEPARATOR = "\n\n\n *** \n"
+
if issues_found == 0:
return (
'## :white_check_mark:'
@@ -8,8 +22,13 @@ def generate_comment(comment_title, content, issues_found, tool_name):
expected_comment_body = (
f'##
:zap: {comment_title} :zap:
\n\n'
)
- if tool_name == "clang-tidy":
- expected_comment_body += "\n\n *** \n"
+
+ if tool_name == "cppcheck":
+ expected_comment_body += SEPARATOR
+ elif tool_name == "fbinfer":
+ expected_comment_body += SEPARATOR + SEPARATOR
+ elif tool_name == "clang-tidy":
+ expected_comment_body += SEPARATOR + SEPARATOR + SEPARATOR
expected_comment_body += (
f" :red_circle: {tool_name} found "
@@ -18,9 +37,29 @@ def generate_comment(comment_title, content, issues_found, tool_name):
f"{content}
"
)
- if tool_name == "cppcheck":
- expected_comment_body += "\n\n *** \n"
+ if tool_name == "flawfinder":
+ expected_comment_body += SEPARATOR + SEPARATOR + SEPARATOR
+ elif tool_name == "cppcheck":
+ expected_comment_body += SEPARATOR + SEPARATOR
+ elif tool_name == "fbinfer":
+ expected_comment_body += SEPARATOR
else:
expected_comment_body += "
\n"
return expected_comment_body
+
+def genhashes(to_join):
+ hashes = []
+
+ for sarif_run in to_join:
+ with open(sarif_run) as file:
+ sarif_json = json.load(file)
+
+ schema_key = "$schema"
+ del sarif_json[schema_key]
+
+ sarif = SarifLog(**sarif_json)
+ for run in sarif.runs:
+ hashes.append(hash(json.dumps(run, sort_keys=True, ensure_ascii=True, default=lambda v: repr(v) + str(hash(v)))))
+
+ return hashes