From f46c2a31f69786f82762fb93043a90cfe25bd5c1 Mon Sep 17 00:00:00 2001 From: Maarten van der Schrieck Date: Sat, 6 Jan 2024 16:00:51 +0100 Subject: [PATCH] add test/cli/test-json.py: Schema validation for JSON files. test-json.py uses jsonschema (https://pypi.org/project/jsonschema/) to validate JSON files against schemas, optionally with custom format checkers. All JSON files in the project must be accounted for in test-json.schema_mapping(), either as a schema, or as a file to be validated against a schema. The JSON schemas are implicitly validated against the most recent metaschema available in jsonschema. This commit adds schemas for JSON addon files and for namingng config files. TODO: - runtime schema validation - remove current ad-hoc validation - generate documentation from schemas --- .github/workflows/CI-unixish.yml | 1 + .github/workflows/CI-windows.yml | 2 + .github/workflows/asan.yml | 1 + .github/workflows/tsan.yml | 1 + .github/workflows/ubsan.yml | 1 + addons/addon-namingng-config.schema.json | 141 +++++++++++++++++++++++ addons/addon.schema.json | 35 ++++++ addons/namingng.config.json | 5 +- test/cli/test-json.py | 136 ++++++++++++++++++++++ test/cli/test-other.py | 3 + 10 files changed, 325 insertions(+), 1 deletion(-) create mode 100644 addons/addon-namingng-config.schema.json create mode 100644 addons/addon.schema.json create mode 100644 test/cli/test-json.py diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index e28f435fd850..5259e0601bbc 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -367,6 +367,7 @@ jobs: python3 -m pip install pip --upgrade python3 -m pip install pytest python3 -m pip install pytest-timeout + python3 -m pip install jsonschema - name: Build cppcheck run: | diff --git a/.github/workflows/CI-windows.yml b/.github/workflows/CI-windows.yml index 1de7af8d99d8..c67def6ab8a0 100644 --- a/.github/workflows/CI-windows.yml +++ b/.github/workflows/CI-windows.yml @@ -134,6 +134,7 @@ jobs: python -m pip install pytest || exit /b !errorlevel! python -m pip install pytest-custom_exit_code || exit /b !errorlevel! python -m pip install pytest-timeout || exit /b !errorlevel! + python -m pip install jsonschema || exit /b !errorlevel! - name: Run CMake if: false # TODO: enable @@ -173,6 +174,7 @@ jobs: python -m pytest -Werror --strict-markers -vv test-inline-suppress.py || exit /b !errorlevel! python -m pytest -Werror --strict-markers -vv test-more-projects.py || exit /b !errorlevel! python -m pytest -Werror --strict-markers -vv test-other.py || exit /b !errorlevel! + python -m pytest -Werror --strict-markers -vv test-json.py || exit /b !errorlevel! python -m pytest -Werror --strict-markers -vv test-proj2.py || exit /b !errorlevel! python -m pytest -Werror --strict-markers -vv test-suppress-syntaxError.py || exit /b !errorlevel! diff --git a/.github/workflows/asan.yml b/.github/workflows/asan.yml index cc2f4a405e88..3255cb3ca64a 100644 --- a/.github/workflows/asan.yml +++ b/.github/workflows/asan.yml @@ -64,6 +64,7 @@ jobs: python3 -m pip install pip --upgrade python3 -m pip install pytest python3 -m pip install pytest-timeout + python3 -m pip install jsonschema # TODO: disable all warnings - name: CMake diff --git a/.github/workflows/tsan.yml b/.github/workflows/tsan.yml index 8c76c507d0d3..863d0d5b86de 100644 --- a/.github/workflows/tsan.yml +++ b/.github/workflows/tsan.yml @@ -64,6 +64,7 @@ jobs: python3 -m pip install pip --upgrade python3 -m pip install pytest python3 -m pip install pytest-timeout + python3 -m pip install jsonschema - name: CMake run: | diff --git a/.github/workflows/ubsan.yml b/.github/workflows/ubsan.yml index a48fcb4a5013..d123b12f1593 100644 --- a/.github/workflows/ubsan.yml +++ b/.github/workflows/ubsan.yml @@ -63,6 +63,7 @@ jobs: python3 -m pip install pip --upgrade python3 -m pip install pytest python3 -m pip install pytest-timeout + python3 -m pip install jsonschema # TODO: disable warnings - name: CMake diff --git a/addons/addon-namingng-config.schema.json b/addons/addon-namingng-config.schema.json new file mode 100644 index 000000000000..3d166665eabc --- /dev/null +++ b/addons/addon-namingng-config.schema.json @@ -0,0 +1,141 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cppcheck.com/addon.schema.json", + "title": "Addon namingng config", + "description": "namingng configuration format", + "type": "object", + "properties": { + "RE_FILE": { + "description": "Patterns for filenames", + "$ref": "#/definitions/re_list_or_none" + }, + "RE_NAMESPACE": { + "description": "Patterns for namespaces", + "$ref": "#/definitions/re_list_or_dict_or_none" + }, + "RE_VARNAME": { + "description": "Patterns for variable names", + "$ref": "#/definitions/re_list_or_dict_or_none" + }, + "RE_PRIVATE_MEMBER_VARIABLE": { + "description": "Patterns for private member variable names", + "$ref": "#/definitions/re_list_or_dict_or_none" + }, + "RE_PUBLIC_MEMBER_VARIABLE": { + "description": "Patterns for public member variable names", + "$ref": "#/definitions/re_list_or_dict_or_none" + }, + "RE_GLOBAL_VARNAME": { + "description": "Patterns for global variable names", + "$ref": "#/definitions/re_list_or_dict_or_none" + }, + "RE_FUNCTIONNAME": { + "description": "Patterns for function names", + "$ref": "#/definitions/re_list_or_dict_or_none" + }, + "RE_CLASS_NAME": { + "description": "Patterns for class names", + "$ref": "#/definitions/re_list_or_dict_or_none" + }, + "var_prefixes": { + "description": "Variable prefixes per type", + "$ref": "#/definitions/prefix_dict" + }, + "function_prefixes": { + "description": "Variable prefixes per return type", + "$ref": "#/definitions/prefix_dict" + }, + "skip_one_char_variables": { + "description": "Whether to ignore one-character local variables (default: false)", + "type":"boolean" + }, + "include_guard": { + "properties": { + "input": { + "description": "What to take as input for the include guard name (default: path)", + "enum": ["path","basename"] + }, + "case": { + "description": "What case the include guard name is in (default: upper)", + "enum": ["upper","lower","keep"] + }, + "prefix": { + "description": "Include guard prefix", + "type": "string" + }, + "suffix": { + "description": "Include guard suffix", + "type": "string" + }, + "max_linenr": { + "description": "Don't consider include guards found after this line number (default: 5)", + "type": "number" + }, + "required": { + "description": "Whether include guards are required for include files (default: true)", + "type": "boolean" + }, + "RE_HEADERFILE": { + "description": "Pattern used to determine whether a file is an include file (default: relative paths ending in .h)", + "$ref": "#/definitions/python_re" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": [], + "definitions": { + "re_list_or_dict_or_none":{ + "anyOf": [ + { "$ref": "#/definitions/re_list" }, + { "$ref": "#/definitions/re_dict" }, + { "type": "null" } + ] + }, + "re_list_or_none":{ + "anyOf": [ + { "$ref": "#/definitions/re_list" }, + { "type": "null" } + ] + }, + "re_list": { + "type": "array", + "items": { + "$ref": "#/definitions/python_re" + }, + "minItems":1 + }, + "re_dict": { + "type": "object", + "propertyNames": { + "$ref": "#/definitions/python_re" + }, + "patternProperties": { + "": { + "type": "array", + "prefixItems": [ + { "type": "boolean" }, + { "type": "string" } + ], + "minItems": 2, + "unevaluatedItems": false + } + } + }, + "prefix_dict": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9_*&]+$": { + "type":"string", + "pattern": "^[a-zA-Z0-9_]+$" + } + }, + "additionalProperties": false + }, + "python_re": { + "type": "string", + "format": "python_re" + } + } +} diff --git a/addons/addon.schema.json b/addons/addon.schema.json new file mode 100644 index 000000000000..9804f75a1618 --- /dev/null +++ b/addons/addon.schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cppcheck.com/addon.schema.json", + "title": "Addon", + "description": "Cppcheck addon format", + "type": "object", + "properties": { + "script": { + "description": "The Python script containing the addon code.", + "type": "string" + }, + "args": { + "description": "Arguments to pass to the script.", + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "python": { + "description": "The full path of the Python interpreter to use.", + "type": "string" + }, + "ctu": { + "description": "Whether the addon is instructed to perform analysis across multiple translation units.", + "type": "boolean" + }, + "executable": { + "description": "Override default 'python