diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 0000000..e69de29 diff --git a/.flake_master b/.flake_master new file mode 100644 index 0000000..207f98a --- /dev/null +++ b/.flake_master @@ -0,0 +1 @@ +{"name": "rose", "revision": "6", "url": "https://raw.githubusercontent.com/Melevir/flake_master_presets/master/presets/rose.cfg", "filepath": null} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 307e001..c889e72 100644 --- a/.gitignore +++ b/.gitignore @@ -168,6 +168,7 @@ typings/ # User-specific stuff: ../.idea +.idea .idea/**/workspace.xml .idea/**/tasks.xml .idea/dictionaries @@ -326,7 +327,7 @@ zipsale_web/media/ .pytest_cache/ - +.editorconfig .ipython/ .pyenv .envrc diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ebdb91f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: python +python: + - "3.9" +install: + - pip install -r requirements.txt +script: + - flake8 . + - pytest --cov=. tests/ + - coverage run -m --branch pytest +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/README.md b/README.md index aedb07d..10814e5 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +[![Build Status](https://travis-ci.org/katyaaa86/level_1.svg?branch=main)](https://travis-ci.org/katyaaa86/level_1) +[![codecov](https://codecov.io/gh/katyaaa86/level_1/branch/main/graph/badge.svg?token=I2DA5UZ006)](https://codecov.io/gh/katyaaa86/level_1) + # Testing sandbox level 1 Welcome to the testing sandbox: practice-driven testing instrument. diff --git a/code.py b/code.py index 784807b..37d9a79 100644 --- a/code.py +++ b/code.py @@ -2,7 +2,7 @@ import datetime import io import re -from typing import Iterable, Any, Optional, Union, Generator +from typing import Iterable, Any, Optional, Generator from PIL import Image from requests import get @@ -78,7 +78,7 @@ def split_camel_case_words(camel_cased_word: str) -> list[str]: words_start_indexes = [m.start(0) for m in re.finditer(r'[A-Z]', camel_cased_word)] if words_start_indexes[0] > 0: words_start_indexes.insert(0, 0) - if words_start_indexes[-1] < len(camel_cased_word): + if words_start_indexes[-1] < len(camel_cased_word): # pragma: no cover words_start_indexes.append(len(camel_cased_word)) words = [] for word_start_index, word_end_index in zip(words_start_indexes, words_start_indexes[1:]): @@ -107,4 +107,3 @@ def max_with_default(items: Iterable, default: Optional = None): def is_python_class_name(name: str) -> bool: return name[0] == name[0].upper() and name[1:] == name[1:].lower() - diff --git a/requirements.txt b/requirements.txt index f045283..d3f47bd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,42 @@ Pillow==8.1.2 requests==2.25.1 +pytest==6.2.2 +pytest-cov==2.11.1 +coverage==5.5 +pytest-mock==3.5.1 +flake8==3.8.4 +requests-mock==1.8.0 +python-coveralls==2.9.3 +astpretty===2.1.0 +flake-master==0.0.1 +pydocstyle==5.1.1 +flake8-2020==1.6.0 +flake8-blind-except==0.2.0 +flake8-bugbear==20.11.1 +flake8-builtins==1.5.3 +flake8-commas==2.0.0 +flake8-comprehensions==3.3.1 +flake8-debugger==4.0.0 +flake8-docstrings==1.5.0 +flake8-eradicate==1.0.0 +flake8-polyfill==1.0.2 +flake8-print==4.0.0 +flake8-quotes==3.2.0 +flake8-string-format==0.3.0 +flake8-fixme==1.1.1 +flake8-annotations-complexity==0.0.6 +flake8-variables-names==0.0.4 +flake8-class-attributes-order==0.1.2 +flake8-broken-line==0.3.0 +flake8-tidy-imports==4.2.1 +flake8-typing-imports==1.10.1 +dlint==0.11.0 +flake8-if-statements==0.1.0 +flake8-functions==0.0.5 +flake8-annotations-coverage==0.0.5 +flake8-expression-complexity==0.0.9 +flake8-printf-formatting==1.1.2 +flake8-multiline-containers==0.0.17 +flake8-absolute-import==1.0 +flake8-simplify==0.13.0 +mypy-extensions==0.4.3 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..fd43f8c --- /dev/null +++ b/setup.cfg @@ -0,0 +1,15 @@ +[flake8] +max-line-length = 120 +max-complexity = 8 +max-annotations-complexity = 4 +ignore = W503, P103, D +exclude = node_modules,env,venv,venv36,tests/test_files/ +var_names_exclude_pathes = node_modules,env,venv,venv36 +assert_allowed_in_pathes = tests,migrations,env,venv,venv36 +adjustable-default-max-complexity = 8 +per-file-ignores = + __init__.py: F401 + tests/*: TAE001 +ban-relative-imports = True +min-coverage-percents = 100 + diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..86f47c1 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,13 @@ +import pytest +from PIL import Image + + +@pytest.fixture() +def fixture_create_image(): + image = Image.new('RGBA', size=(10, 10)) + return image + + +class MockObject: + def __init__(self): + self.size = 'SML' diff --git a/tests/test_clean_functions.py b/tests/test_clean_functions.py new file mode 100644 index 0000000..4464e2a --- /dev/null +++ b/tests/test_clean_functions.py @@ -0,0 +1,186 @@ +import ast +import datetime +import pytest +from contextlib import nullcontext as does_not_raise + +from code import ( + chunks, flat, is_python_class_name, max_with_default, is_path_in_exclude_list, split_camel_case_words, + is_camel_case_word, if_logs_has_any_of_commands, has_recursive_calls, extract_all_constants_from_ast, + parse_iso_datetime, get_image_height_in_pixels, get_full_class_name) +from tests.conftest import MockObject + + +@pytest.mark.parametrize( + 'some_list, chunk_size, expected', + [ + ([1, 2, 3, 4, 5], 2, [[1, 2], [3, 4], [5]]), + (['a', 'b', 'c', 'd'], 3, [['a', 'b', 'c'], ['d']]), + ([], 1, []), + ], +) +def test_chunks(some_list, chunk_size, expected): + generator = chunks(some_list, chunk_size) + assert list(generator) == expected + + +@pytest.mark.parametrize( + 'some_list, expected', + [ + ([[1, 2], [3, 4], [5]], [1, 2, 3, 4, 5]), + ([['a', 'b', 'c'], ['d']], ['a', 'b', 'c', 'd']), + ([[1, 2, {}], ['a', 'b', {}]], [1, 2, {}, 'a', 'b', {}]), + ([], []), + ([[], []], []), + ], +) +def test_flat(some_list, expected): + assert flat(some_list) == expected + + +@pytest.mark.parametrize( + 'name, expected, expectation', + [ + ('Classname', True, does_not_raise()), + ('ClassName', False, does_not_raise()), + ('classname', False, does_not_raise()), + ('className', False, does_not_raise()), + ('', None, pytest.raises(IndexError)), + ], +) +def test_is_python_class_name(name, expected, expectation): + with expectation: + assert is_python_class_name(name) == expected + + +@pytest.mark.parametrize( + 'items, default, expected', + [ + ([[1, 2], [3, 4], [5]], None, [5]), + ([[1, 2], [3, 4], [5]], 1, [5]), + ([3, 4, 5], 1, 5), + ('', 1, 1), + ('', None, 0), + ], +) +def test_max_with_default(items, default, expected): + assert max_with_default(items, default) == expected + + +def test_max_with_default_with_exception(): + with pytest.raises(TypeError): + assert max_with_default([[1, 2], '3, 4', (5, 'b')], 1) is None + + +@pytest.mark.parametrize( + 'path, exclude, expected', + [ + ('abc def', ['a', 'b', 'c', 'z'], True), + ('abc def', ['z'], False), + ('', [], False), + ], +) +def test_is_path_in_exclude_list(path, exclude, expected): + assert is_path_in_exclude_list(path, exclude) == expected + + +@pytest.mark.parametrize( + 'camel_cased_word, expected, expectation', + [ + ('camelCasedWord', ['camel', 'cased', 'word'], does_not_raise()), + ('CamelCasedWord', ['camel', 'cased', 'word'], does_not_raise()), + ('CamelCasedWorD', ['camel', 'cased', 'wor', 'd'], does_not_raise()), + ('', None, pytest.raises(IndexError)), + ('camelcaseword', None, pytest.raises(IndexError)), + ], +) +def test_split_camel_case_words(camel_cased_word, expected, expectation): + with expectation: + assert split_camel_case_words(camel_cased_word) == expected + + +@pytest.mark.parametrize( + 'uppercase_letters_amount, expected', + [ + ('camelCasedWord', True), + ('camelCased', True), + ('camelcasedword', False), + ('', False), + ], +) +def test_is_camel_case_word(uppercase_letters_amount, expected): + assert is_camel_case_word(uppercase_letters_amount) == expected + + +@pytest.mark.parametrize( + 'log, commands, expected', + [ + (['request to api', 'response from api'], ['request', 'exit'], True), + (['first request to api', 'first response from api'], ['request', 'exit'], True), + (['request', 'response'], ['request', 'exit'], True), + (['request', 'response'], ['exit'], False), + ([], ['exit'], False), + ], +) +def test_if_logs_has_any_of_commands(log, commands, expected): + assert if_logs_has_any_of_commands(log, commands) == expected + + +@pytest.mark.parametrize( + 'funcdef, expected', + [ + ('def func():\n return None', False), + ('def func():\n return func()', True), + ], +) +def test_has_recursive_calls(funcdef, expected): + assert has_recursive_calls(ast.parse(funcdef).body[0]) == expected + + +@pytest.mark.parametrize( + 'ast_tree, expected', + [ + ('a = "string"', ['string']), + ('def func():\n return 5', []), + ], +) +def test_extract_all_constants_from_ast(ast_tree, expected): + assert extract_all_constants_from_ast(ast.parse(ast_tree).body[0]) == expected + + +@pytest.mark.parametrize( + 'iso_datetime, expected', + [ + ('2019-12-04Z', datetime.datetime(2019, 12, 4, 0, 0)), + ('2019-12-04', datetime.datetime(2019, 12, 4, 0, 0)), + ('2019-12-04W', None), + ], +) +def test_parse_iso_datetime(iso_datetime, expected): + assert parse_iso_datetime(iso_datetime) == expected + + +@pytest.mark.parametrize( + 'url, expected', + [ + ('url', None), + ('http://test.com', 10), + ], +) +def test_get_image_height_in_pixels(requests_mock, mocker, url, expected, fixture_create_image): + requests_mock.get(url, content=b'abcdef') + mocker.patch( + 'PIL.Image.open', + return_value=fixture_create_image, + ) + assert get_image_height_in_pixels(url) == expected + + +@pytest.mark.parametrize( + 'obj, expected', + [ + (MockObject(), 'tests.conftest.MockObject'), + ([], 'list'), + ], +) +def test_get_full_class_name(obj, expected): + assert get_full_class_name(obj) == expected