From 48961621c0cc7d75d974280c5b55e32bc8182767 Mon Sep 17 00:00:00 2001 From: Oleksii Proshchenko <80070761+MasterpieceElbow@users.noreply.github.com> Date: Wed, 5 Jan 2022 15:39:47 +0200 Subject: [PATCH] Add linter_formatter GitHub task --- README.md | 204 +++++++++++++++++++++++++- app/main.py | 16 +- tests/test_main.py | 353 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 565 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 72e3fdea..3930d1e7 100644 --- a/README.md +++ b/README.md @@ -1 +1,203 @@ -# Python boilerplate for GitHub tasks \ No newline at end of file +Линтер flake8 выдает такой отчет об ошибках при проверке каталога с файлами исходного +кода: +```python +errors = { + "./test_source_code_2.py": [], + "./source_code_2.py": [ + { + "code": "E501", + "filename": "./source_code_2.py", + "line_number": 18, + "column_number": 80, + "text": "line too long (99 > 79 characters)", + "physical_line": ' return f"I like to filter, rounding, doubling, ' + "store and decorate numbers: {', '.join(items)}!\"", + }, + { + "code": "W292", + "filename": "./source_code_2.py", + "line_number": 18, + "column_number": 100, + "text": "no newline at end of file", + "physical_line": ' return f"I like to filter, rounding, doubling, ' + "store and decorate numbers: {', '.join(items)}!\"", + }, + ], + "./source_code_1.py": [ + { + "code": "E702", + "filename": "./source_code_1.py", + "line_number": 3, + "column_number": 74, + "text": "multiple statements on one line (semicolon)", + "physical_line": ' new_items = [f"{key} -> {value}" for key, ' + "value in items.items()]; return func(new_items)\n", + }, + { + "code": "E501", + "filename": "./source_code_1.py", + "line_number": 3, + "column_number": 80, + "text": "line too long (97 > 79 characters)", + "physical_line": ' new_items = [f"{key} -> {value}" for key, ' + "value in items.items()]; return func(new_items)\n", + }, + { + "code": "E302", + "filename": "./source_code_1.py", + "line_number": 15, + "column_number": 1, + "text": "expected 2 blank lines, found 1", + "physical_line": "def number_filter(func):\n", + }, + { + "code": "E303", + "filename": "./source_code_1.py", + "line_number": 27, + "column_number": 1, + "text": "too many blank lines (6)", + "physical_line": "@number_filter\n", + }, + { + "code": "E501", + "filename": "./source_code_1.py", + "line_number": 31, + "column_number": 80, + "text": "line too long (99 > 79 characters)", + "physical_line": ' return f"I like to filter, rounding, doubling, ' + "store and decorate numbers: {', '.join(items)}!\"\n", + }, + ], + "./test_source_code_1.py": [ + { + "code": "E302", + "filename": "./test_source_code_1.py", + "line_number": 4, + "column_number": 1, + "text": "expected 2 blank lines, found 1", + "physical_line": "@pytest.mark.parametrize(\n", + }, + { + "code": "E501", + "filename": "./test_source_code_1.py", + "line_number": 32, + "column_number": 80, + "text": "line too long (84 > 79 characters)", + "physical_line": ' "decorate numbers: 1 -> 2, 2 -> 4, 6 -> 12, -111 -> -222, -50 -> -100!",\n', + }, + { + "code": "W292", + "filename": "./test_source_code_1.py", + "line_number": 112, + "column_number": 6, + "text": "no newline at end of file", + "physical_line": " )", + }, + ], +} +``` +Здесь `errors` это словарь, в котором ключи - путь к файлу, а значение этого ключа - +список ошибок, где каждая ошибка - словарь + +Но мы храним результаты выполнения немного в другом формате. +Напиши 3 функции, чтобы преобразовать отчет в нужном нам формате. +1. `format_linter_error` - форматирует отдельно взятую ошибку +2. `format_single_linter_file` - форматирует все ошибки для конкретного файла +3. `format_linter_report` - форматирует все ошибки для всех файлов в отчете + +Все функции должны вмещать в себе только конструкцию `return` + +Требуемый формат хранения: +```python +errors = [ + {"errors": [], "path": "./test_source_code_2.py", "status": "passed"}, + { + "errors": [ + { + "line": 18, + "column": 80, + "message": "line too long (99 > 79 characters)", + "name": "E501", + "source": "flake8", + }, + { + "line": 18, + "column": 100, + "message": "no newline at end of file", + "name": "W292", + "source": "flake8", + }, + ], + "path": "./source_code_2.py", + "status": "failed", + }, + { + "errors": [ + { + "line": 3, + "column": 74, + "message": "multiple statements on one line (semicolon)", + "name": "E702", + "source": "flake8", + }, + { + "line": 3, + "column": 80, + "message": "line too long (97 > 79 characters)", + "name": "E501", + "source": "flake8", + }, + { + "line": 15, + "column": 1, + "message": "expected 2 blank lines, found 1", + "name": "E302", + "source": "flake8", + }, + { + "line": 27, + "column": 1, + "message": "too many blank lines (6)", + "name": "E303", + "source": "flake8", + }, + { + "line": 31, + "column": 80, + "message": "line too long (99 > 79 characters)", + "name": "E501", + "source": "flake8", + }, + ], + "path": "./source_code_1.py", + "status": "failed", + }, + { + "errors": [ + { + "line": 4, + "column": 1, + "message": "expected 2 blank lines, found 1", + "name": "E302", + "source": "flake8", + }, + { + "line": 32, + "column": 80, + "message": "line too long (84 > 79 characters)", + "name": "E501", + "source": "flake8", + }, + { + "line": 112, + "column": 6, + "message": "no newline at end of file", + "name": "W292", + "source": "flake8", + }, + ], + "path": "./test_source_code_1.py", + "status": "failed", + }, +] +``` \ No newline at end of file diff --git a/app/main.py b/app/main.py index 773a588b..c6118f8e 100644 --- a/app/main.py +++ b/app/main.py @@ -1,3 +1,13 @@ -# TODO: add initial code -def hello_world(): - return "Hello, world!" +def format_linter_error(error: dict) -> dict: + # white your code here + pass + + +def format_single_linter_file(file_path: str, errors: list) -> dict: + # white your code here + pass + + +def format_linter_report(linter_report: dict) -> list: + # white your code here + pass diff --git a/tests/test_main.py b/tests/test_main.py index 6d8b3235..2d33aa41 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,6 +1,351 @@ -# TODO: add tests -from app.main import hello_world +import pytest +import ast +import inspect +from app.main import ( + format_linter_error, + format_single_linter_file, + format_linter_report, +) -def test_hello_world(): - assert hello_world() == "Hello, world!" + +@pytest.mark.parametrize( + "error_linter,error_mate", + [ + ( + { + "code": "E501", + "filename": "./source_code_2.py", + "line_number": 18, + "column_number": 80, + "text": "line too long (99 > 79 characters)", + "physical_line": ' return f"I like to filter, rounding, doubling, ' + "store and decorate numbers: {', '.join(items)}!\"", + }, + { + "line": 18, + "column": 80, + "message": "line too long (99 > 79 characters)", + "name": "E501", + "source": "flake8", + }, + ), + ( + { + "code": "E702", + "filename": "./source_code_1.py", + "line_number": 3, + "column_number": 74, + "text": "multiple statements on one line (semicolon)", + "physical_line": ' new_items = [f"{key} -> {value}" for key, ' + "value in items.items()]; return func(new_items)\n", + }, + { + "line": 3, + "column": 74, + "message": "multiple statements on one line (semicolon)", + "name": "E702", + "source": "flake8", + }, + ), + ( + { + "code": "E302", + "filename": "./source_code_1.py", + "line_number": 15, + "column_number": 1, + "text": "expected 2 blank lines, found 1", + "physical_line": "def number_filter(func):\n", + }, + { + "line": 15, + "column": 1, + "message": "expected 2 blank lines, found 1", + "name": "E302", + "source": "flake8", + }, + ), + ], +) +def test_format_linter_error(error_linter, error_mate): + assert format_linter_error(error_linter) == error_mate, ( + f"Function 'format_linter_error' should return {error_mate}, " + f"when 'error' equals to {error_linter}" + ) + + +def test_format_linter_error_one_line(): + code = inspect.getsource(format_linter_error) + assert ( + isinstance(ast.parse(code).body[0].body[0], ast.Return) is True + ), "Function 'format_linter_error' should contain only return statement" + + +@pytest.mark.parametrize( + "file_path,errors,result", + [ + ( + "./source_code_2.py", + [ + { + "code": "E501", + "filename": "./source_code_2.py", + "line_number": 18, + "column_number": 80, + "text": "line too long (99 > 79 characters)", + "physical_line": ' return f"I like to filter, rounding, doubling, ' + "store and decorate numbers: {', '.join(items)}!\"", + }, + { + "code": "W292", + "filename": "./source_code_2.py", + "line_number": 18, + "column_number": 100, + "text": "no newline at end of file", + "physical_line": ' return f"I like to filter, rounding, doubling, ' + "store and decorate numbers: {', '.join(items)}!\"", + }, + ], + { + "errors": [ + { + "line": 18, + "column": 80, + "message": "line too long (99 > 79 characters)", + "name": "E501", + "source": "flake8", + }, + { + "line": 18, + "column": 100, + "message": "no newline at end of file", + "name": "W292", + "source": "flake8", + }, + ], + "path": "./source_code_2.py", + "status": "failed", + }, + ) + ], +) +def test_format_single_linter_file(file_path, errors, result): + assert format_single_linter_file(file_path, errors) == result, ( + f"Function 'format_single_linter_file' should return {result}, " + f"when 'file_path' equals to {file_path}, " + f"and 'errors' equals to {errors}" + ) + + +def test_format_single_linter_file_one_line(): + code = inspect.getsource(format_single_linter_file) + assert ( + isinstance(ast.parse(code).body[0].body[0], ast.Return) is True + ), "Function 'format_single_linter_file' should contain only return statement" + + +@pytest.mark.parametrize( + "errors_linter,errors_mate", + [ + ( + { + "./test_source_code_2.py": [], + "./source_code_2.py": [ + { + "code": "E501", + "filename": "./source_code_2.py", + "line_number": 18, + "column_number": 80, + "text": "line too long (99 > 79 characters)", + "physical_line": ' return f"I like to filter, rounding, doubling, ' + "store and decorate numbers: {', '.join(items)}!\"", + }, + { + "code": "W292", + "filename": "./source_code_2.py", + "line_number": 18, + "column_number": 100, + "text": "no newline at end of file", + "physical_line": ' return f"I like to filter, rounding, doubling, ' + "store and decorate numbers: {', '.join(items)}!\"", + }, + ], + "./source_code_1.py": [ + { + "code": "E702", + "filename": "./source_code_1.py", + "line_number": 3, + "column_number": 74, + "text": "multiple statements on one line (semicolon)", + "physical_line": ' new_items = [f"{key} -> {value}" for key, ' + "value in items.items()]; return func(new_items)\n", + }, + { + "code": "E501", + "filename": "./source_code_1.py", + "line_number": 3, + "column_number": 80, + "text": "line too long (97 > 79 characters)", + "physical_line": ' new_items = [f"{key} -> {value}" for key, ' + "value in items.items()]; return func(new_items)\n", + }, + { + "code": "E302", + "filename": "./source_code_1.py", + "line_number": 15, + "column_number": 1, + "text": "expected 2 blank lines, found 1", + "physical_line": "def number_filter(func):\n", + }, + { + "code": "E303", + "filename": "./source_code_1.py", + "line_number": 27, + "column_number": 1, + "text": "too many blank lines (6)", + "physical_line": "@number_filter\n", + }, + { + "code": "E501", + "filename": "./source_code_1.py", + "line_number": 31, + "column_number": 80, + "text": "line too long (99 > 79 characters)", + "physical_line": ' return f"I like to filter, rounding, doubling, ' + "store and decorate numbers: {', '.join(items)}!\"\n", + }, + ], + "./test_source_code_1.py": [ + { + "code": "E302", + "filename": "./test_source_code_1.py", + "line_number": 4, + "column_number": 1, + "text": "expected 2 blank lines, found 1", + "physical_line": "@pytest.mark.parametrize(\n", + }, + { + "code": "E501", + "filename": "./test_source_code_1.py", + "line_number": 32, + "column_number": 80, + "text": "line too long (84 > 79 characters)", + "physical_line": ' "decorate numbers: 1 -> 2, 2 -> 4, 6 -> 12, -111 -> -222, -50 -> ' + '-100!",\n', + }, + { + "code": "W292", + "filename": "./test_source_code_1.py", + "line_number": 112, + "column_number": 6, + "text": "no newline at end of file", + "physical_line": " )", + }, + ], + }, + [ + {"errors": [], "path": "./test_source_code_2.py", "status": "passed"}, + { + "errors": [ + { + "line": 18, + "column": 80, + "message": "line too long (99 > 79 characters)", + "name": "E501", + "source": "flake8", + }, + { + "line": 18, + "column": 100, + "message": "no newline at end of file", + "name": "W292", + "source": "flake8", + }, + ], + "path": "./source_code_2.py", + "status": "failed", + }, + { + "errors": [ + { + "line": 3, + "column": 74, + "message": "multiple statements on one line (semicolon)", + "name": "E702", + "source": "flake8", + }, + { + "line": 3, + "column": 80, + "message": "line too long (97 > 79 characters)", + "name": "E501", + "source": "flake8", + }, + { + "line": 15, + "column": 1, + "message": "expected 2 blank lines, found 1", + "name": "E302", + "source": "flake8", + }, + { + "line": 27, + "column": 1, + "message": "too many blank lines (6)", + "name": "E303", + "source": "flake8", + }, + { + "line": 31, + "column": 80, + "message": "line too long (99 > 79 characters)", + "name": "E501", + "source": "flake8", + }, + ], + "path": "./source_code_1.py", + "status": "failed", + }, + { + "errors": [ + { + "line": 4, + "column": 1, + "message": "expected 2 blank lines, found 1", + "name": "E302", + "source": "flake8", + }, + { + "line": 32, + "column": 80, + "message": "line too long (84 > 79 characters)", + "name": "E501", + "source": "flake8", + }, + { + "line": 112, + "column": 6, + "message": "no newline at end of file", + "name": "W292", + "source": "flake8", + }, + ], + "path": "./test_source_code_1.py", + "status": "failed", + }, + ], + ) + ], +) +def test_format_linter_report(errors_linter, errors_mate): + assert format_linter_report(errors_linter) == errors_mate, ( + f"Function 'format_linter_report' should return {errors_mate} " + f"when 'errors' equals to {errors_linter}" + ) + + +def test_format_linter_report_one_line(): + code = inspect.getsource(format_linter_report) + assert ( + isinstance(ast.parse(code).body[0].body[0], ast.Return) is True + ), "Function 'format_linter_report' should contain only return statement"