From 1c1254c43f69829ba8ce611c640e86ce0bae2ad2 Mon Sep 17 00:00:00 2001 From: Jannis Mittenzwei Date: Fri, 6 Dec 2024 12:55:19 +0100 Subject: [PATCH] nox task for checking linting files added + tests --- exasol/toolbox/nox/_gh.py | 94 ++++++++++++++++++ exasol/toolbox/nox/tasks.py | 5 +- poetry.lock | 20 ++-- test/unit/lint_file_check_test.py | 153 ++++++++++++++++++++++++++++++ 4 files changed, 260 insertions(+), 12 deletions(-) create mode 100644 exasol/toolbox/nox/_gh.py create mode 100644 test/unit/lint_file_check_test.py diff --git a/exasol/toolbox/nox/_gh.py b/exasol/toolbox/nox/_gh.py new file mode 100644 index 000000000..a6e1b1bd3 --- /dev/null +++ b/exasol/toolbox/nox/_gh.py @@ -0,0 +1,94 @@ +import nox +from nox import Session +from noxconfig import PROJECT_CONFIG +import sys +from pathlib import Path +import json +import sqlite3 +from typing import Iterable + + +@nox.session(name="check:lint-files", python=False) +def check_lint_files(session: Session) -> None: + """task to validate linting files""" + if not_available := files_not_available( + [".lint.json", ".lint.txt", ".security.json", ".coverage"], + PROJECT_CONFIG.root): + print(f"not available: {not_available}") + sys.exit(1) + + error = False + if msg := check_lint_json(PROJECT_CONFIG.root): + print(f"error in [.lint.json]: {msg}") + error = True + if msg := check_security_json(PROJECT_CONFIG.root): + print(f"error in [.security.json]: {msg}") + error = True + if msg := check_coverage(PROJECT_CONFIG.root): + print(f"error in [.coverage]: {msg}") + error = True + if error: + sys.exit(1) + + +def files_not_available(requested: list, path: Path) -> list: + not_existing_files = requested.copy() + for file in path.iterdir(): + if file.is_file(): + if file.name in not_existing_files: + not_existing_files.remove(file.name) + return not_existing_files + + +def check_lint_json(path: Path) -> str: + path = Path(path, ".lint.json") + try: + with path.open("r") as file: + issues = json.load(file) + error = False + for issue in issues: + attributes = ["type", "module", "obj", "line", "column", "endLine", + "endColumn", "path", "symbol", "message", "message-id"] + for attribute in attributes: + error |= attribute not in issue + if error: + return "incompatible format" + return "" + + except json.JSONDecodeError: + return "parsing of json not possible" + + +def check_security_json(path: Path) -> str: + path = Path(path, ".security.json") + try: + with path.open("r") as input_file: + json_file = json.load(input_file) + error = False + attributes = ["errors", "generated_at", "metrics", "results"] + for attribute in attributes: + error |= attribute not in json_file + if error: + return "incompatible format" + return "" + + except json.JSONDecodeError: + return "parsing of json not possible" + + +def check_coverage(path: Path) -> str: + path = Path(path, ".coverage") + try: + conn = sqlite3.connect(path) + cursor = conn.cursor() + not_existing_tables = ["coverage_schema", "meta", "file", "line_bits"] + data = cursor.execute("select name from sqlite_schema where type == 'table'") + for row in data: + if row[0] in not_existing_tables: + not_existing_tables.remove(row[0]) + conn.close() + if not_existing_tables: + return "not existing tables: " + str(not_existing_tables) + return "" + except sqlite3.Error: + return "connection to database not possible" diff --git a/exasol/toolbox/nox/tasks.py b/exasol/toolbox/nox/tasks.py index 3592abbd7..a8bd97850 100644 --- a/exasol/toolbox/nox/tasks.py +++ b/exasol/toolbox/nox/tasks.py @@ -66,7 +66,8 @@ def check(session: Session) -> None: _version, python_files, ) - - +from exasol.toolbox.nox._gh import ( + check_lint_files +) # isort: on # fmt: on diff --git a/poetry.lock b/poetry.lock index a5028593d..275122718 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1148,13 +1148,13 @@ pytest-plugin = ["pytest-prysk (>=0.2.0,<0.3.0)"] [[package]] name = "pydantic" -version = "2.10.2" +version = "2.10.3" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.10.2-py3-none-any.whl", hash = "sha256:cfb96e45951117c3024e6b67b25cdc33a3cb7b2fa62e239f7af1378358a1d99e"}, - {file = "pydantic-2.10.2.tar.gz", hash = "sha256:2bc2d7f17232e0841cbba4641e65ba1eb6fafb3a08de3a091ff3ce14a197c4fa"}, + {file = "pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d"}, + {file = "pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9"}, ] [package.dependencies] @@ -1305,17 +1305,17 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pylint" -version = "3.3.1" +version = "3.3.2" description = "python code static checker" optional = false python-versions = ">=3.9.0" files = [ - {file = "pylint-3.3.1-py3-none-any.whl", hash = "sha256:2f846a466dd023513240bc140ad2dd73bfc080a5d85a710afdb728c420a5a2b9"}, - {file = "pylint-3.3.1.tar.gz", hash = "sha256:9f3dcc87b1203e612b78d91a896407787e708b3f189b5fa0b307712d49ff0c6e"}, + {file = "pylint-3.3.2-py3-none-any.whl", hash = "sha256:77f068c287d49b8683cd7c6e624243c74f92890f767f106ffa1ddf3c0a54cb7a"}, + {file = "pylint-3.3.2.tar.gz", hash = "sha256:9ec054ec992cd05ad30a6df1676229739a73f8feeabf3912c995d17601052b01"}, ] [package.dependencies] -astroid = ">=3.3.4,<=3.4.0-dev0" +astroid = ">=3.3.5,<=3.4.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, @@ -1856,13 +1856,13 @@ files = [ [[package]] name = "typer" -version = "0.14.0" +version = "0.15.1" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." optional = false python-versions = ">=3.7" files = [ - {file = "typer-0.14.0-py3-none-any.whl", hash = "sha256:f476233a25770ab3e7b2eebf7c68f3bc702031681a008b20167573a4b7018f09"}, - {file = "typer-0.14.0.tar.gz", hash = "sha256:af58f737f8d0c0c37b9f955a6d39000b9ff97813afcbeef56af5e37cf743b45a"}, + {file = "typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847"}, + {file = "typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a"}, ] [package.dependencies] diff --git a/test/unit/lint_file_check_test.py b/test/unit/lint_file_check_test.py new file mode 100644 index 000000000..c8f5011fc --- /dev/null +++ b/test/unit/lint_file_check_test.py @@ -0,0 +1,153 @@ +import json + +import pytest +from pathlib import Path +import sqlite3 + +from exasol.toolbox.nox import _gh + +@pytest.mark.parametrize( + "files,requested_files,expected", + [ + ( + [".lint.json", ".lint.txt", ".security.json", ".coverage"], + [".lint.json", ".lint.txt", ".security.json", ".coverage"], + [] + ), ( + [".lint.txt", ".security.json", ".coverage"], + [".lint.json", ".lint.txt", ".security.json", ".coverage"], + [".lint.json"] + ), ( + [".lint.json", ".security.json", ".coverage"], + [".lint.json", ".lint.txt", ".security.json", ".coverage"], + [".lint.txt"] + ), ( + [".lint.json", ".lint.txt", ".coverage"], + [".lint.json", ".lint.txt", ".security.json", ".coverage"], + [".security.json"] + ), ( + [".lint.json", ".lint.txt", ".security.json"], + [".lint.json", ".lint.txt", ".security.json", ".coverage"], + [".coverage"] + ), ( + [], + [".lint.json", ".lint.txt", ".security.json", ".coverage"], + [".lint.json", ".lint.txt", ".security.json", ".coverage"] + ), + ] +) +def test_check_lint_files(files, requested_files, expected, tmp_path): + path = Path(tmp_path) + for file in files: + Path(path, file).touch() + + actual = _gh.files_not_available(requested_files, path) + assert actual == expected + + +@pytest.mark.parametrize( + "attributes,expected", + [ + ( + ["type", "module", "obj", "line", "column", "endLine", + "endColumn", "path", "symbol", "message", "message-id"], + "" + ), ( + ["module", "obj", "line", "column", "endLine", + "endColumn", "path", "symbol", "message", "message-id"], + "incompatible format" + ), ( + ["type", "obj", "line", "column", "endLine", + "endColumn", "path", "symbol", "message", "message-id"], + "incompatible format" + ), ( + ["type", "module", "line", "column", "endLine", + "endColumn", "path", "symbol", "message", "message-id"], + "incompatible format" + ), ( + ["type", "module", "obj", "column", "endLine", + "endColumn", "path", "symbol", "message", "message-id"], + "incompatible format" + ), ( + ["type", "module", "obj", "line", "endLine", + "endColumn", "path", "symbol", "message", "message-id"], + "incompatible format" + ), ( + ["type", "module", "obj", "line", "column", + "endColumn", "path", "symbol", "message", "message-id"], + "incompatible format" + ), ( + ["type", "module", "obj", "line", "column", "endLine", + "path", "symbol", "message", "message-id"], + "incompatible format" + ), ( + ["type", "module", "obj", "line", "column", "endLine", + "endColumn", "symbol", "message", "message-id"], + "incompatible format" + ), ( + ["type", "module", "obj", "line", "column", "endLine", + "endColumn", "path", "message", "message-id"], + "incompatible format" + ), ( + ["type", "module", "obj", "line", "column", "endLine", + "endColumn", "path", "symbol", "message-id"], + "incompatible format" + ), ( + ["type", "module", "obj", "line", "column", "endLine", + "endColumn", "path", "symbol", "message"], + "incompatible format" + ), + ] +) +def test_check_lint_json(attributes, expected, tmp_path): + path = Path(tmp_path, ".lint.json") + path.touch() + attributes_dict = {} + for attribute in attributes: + attributes_dict[attribute] = None + with path.open("w") as file: + json.dump([attributes_dict], file) + actual = _gh.check_lint_json(path.parent) + assert actual == expected + + +@pytest.mark.parametrize( + "attributes,expected", + [ + (['errors', 'generated_at', 'metrics', 'results'], ""), + (["generated_at", "metrics", "results"], "incompatible format"), + (["errors", "metrics", "results"], "incompatible format"), + (["errors", "generated_at", "results"], "incompatible format"), + (["errors", "generated_at", "metrics"], "incompatible format"), + ] +) +def test_check_lint_json(attributes, expected, tmp_path): + path = Path(tmp_path, ".security.json") + path.touch() + attributes_dict = {} + for attribute in attributes: + attributes_dict[attribute] = None + with path.open("w") as file: + json.dump(attributes_dict, file) + actual = _gh.check_security_json(path.parent) + assert actual == expected + + +@pytest.mark.parametrize( + "tables, expected", + [ + (["coverage_schema", "meta", "file", "line_bits"], ""), + (["meta", "file", "line_bits"], "not existing tables: ['coverage_schema']"), + (["coverage_schema", "file", "line_bits"], "not existing tables: ['meta']"), + (["coverage_schema", "meta", "line_bits"], "not existing tables: ['file']"), + (["coverage_schema", "meta", "file", ], "not existing tables: ['line_bits']"), + ] +) +def test_check_coverage(tables, expected, tmp_path): + path = Path(tmp_path, ".coverage") + connection = sqlite3.connect(path) + cursor = connection.cursor() + for table in tables: + cursor.execute(f"CREATE TABLE IF NOT EXISTS {table} (test INTEGER)") + actual = _gh.check_coverage(path.parent) + assert actual == expected