diff --git a/.gitignore b/.gitignore index 307e001..7029c43 100644 --- a/.gitignore +++ b/.gitignore @@ -68,6 +68,7 @@ target/ .venv venv/ ENV/ +env # Rope project settings .ropeproject @@ -348,3 +349,4 @@ make.exe *.json *sqlite3 +.idea diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..42a7498 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ + +language: python +python: + - "3.9" +install: + - pip install -r requirements.txt +script: + - flake8 . + - coverage run -m --branch pytest +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/README.md b/README.md index aedb07d..0029800 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![codecov](https://codecov.io/gh/OdintsovTim/level_1/branch/main/graph/badge.svg?token=XJA8IVH4SS)](https://codecov.io/gh/OdintsovTim/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/conftest.py b/conftest.py new file mode 100644 index 0000000..320e731 --- /dev/null +++ b/conftest.py @@ -0,0 +1,19 @@ +import pytest +from PIL import Image + + +class FakeClass: + pass + + +def get_size(): + return 50 + + +@pytest.fixture +def test_image_factory(): + def create_test_image(a): + image = Image.new('RGBA', size=(get_size(), get_size()), color=(155, 0, 0)) + return image + + return create_test_image diff --git a/requirements.txt b/requirements.txt index f045283..2707a0d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,5 @@ Pillow==8.1.2 requests==2.25.1 +pytest==6.2.2 +coverage==5.5 +flake8==3.8.4 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..6deafc2 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 120 diff --git a/test_code.py b/test_code.py new file mode 100644 index 0000000..633e7c2 --- /dev/null +++ b/test_code.py @@ -0,0 +1,198 @@ +import ast +import datetime + +import pytest +import requests +from PIL import Image + +import code +from conftest import FakeClass, get_size + + +@pytest.mark.parametrize( + 'name, expected', + [('MyClass', False), ('Myclass', True), ('myclass', False), ('mYclass', False)] +) +def test_is_python_class_name(name, expected): + assert code.is_python_class_name(name) == expected + + +@pytest.mark.parametrize( + 'args, expected', + [ + (([], None), 0), + (([],), 0), + (([], 5), 5), + (([-1], None), -1), + (([1, 3, -1, 100], 5), 100), + (((1, 3, -1, 101), 5), 101), + (((1, 3, -1, 101),), 101), + ] +) +def test_max_with_default(args, expected): + assert code.max_with_default(*args) == expected + + +@pytest.mark.parametrize( + 'args, expected, ', + [(((1, 3, -1, '101'),), None)] +) +def test_max_with_default_with_exception(args, expected): + with pytest.raises(TypeError): + assert code.max_with_default(*args) == expected + + +@pytest.mark.parametrize( + 'path, exclude, expected', + [ + ('mypath', [], False), + ('mypath', ['my', 'path'], True), + ('mypath', ['mypath', 'anypath'], True), + ('', [''], True), + ('', [], False), + ] +) +def test_is_path_in_exclude_list(path, exclude, expected): + assert code.is_path_in_exclude_list(path, exclude) == expected + + +@pytest.mark.parametrize( + 'some_list, expected', + [ + ([[1, 2, 3], [4, 5, 6]], [1, 2, 3, 4, 5, 6]), + ([[1, 'b', 3], [{}, 5, 6], ['a', 8, 9]], [1, 'b', 3, {}, 5, 6, 'a', 8, 9]), + ([[], []], []), + ([], []), + ([[]], []), + ] +) +def test_flat(some_list, expected): + assert code.flat(some_list) == expected + + +@pytest.mark.parametrize( + 'iso_datetime, expected', + [ + ('2018-05-25T12:16:14Z', datetime.datetime(2018, 5, 25, 12, 16, 14)), + ('2018-02-25T18:16:15', datetime.datetime(2018, 2, 25, 18, 16, 15)), + ('2018:02-25T18:16:15', None), + ] +) +def test_parse_iso_datetime(iso_datetime, expected): + assert code.parse_iso_datetime(iso_datetime) == expected + + +@pytest.mark.parametrize( + 'log, commands, expected', + [ + ([], [], False), + (['command'], ['command'], True), + (['command'], ['pip'], False), + (['pip command'], ['pip'], True), + (['nice pip command'], ['pip'], True), + (['command'], [], False), + ([], ['command'], False), + ] +) +def test_if_logs_has_any_of_commands(log, commands, expected): + assert code.if_logs_has_any_of_commands(log, commands) == expected + + +@pytest.mark.parametrize( + 'word, expected', + [ + ('small', False), + ('Bs', False), + ('BiG', True), + ('cAmel', True), + ('small', False), + ('BIG', False), + ('', False), + ] +) +def test_is_camel_case_word(word, expected): + assert code.is_camel_case_word(word) == expected + + +@pytest.mark.parametrize( + 'obj, expected', + [ + ('sting', 'str'), + (1, 'int'), + ([2], 'list'), + ((1, 2, 3), 'tuple'), + (FakeClass(), 'conftest.FakeClass'), + ] +) +def test_get_full_class_name(obj, expected): + assert code.get_full_class_name(obj) == expected + + +@pytest.mark.parametrize( + 'some_list, chunk_size, expected', + [ + ([1, 2, 3, 4, 5], 2, [[1, 2], [3, 4], [5]]), + ([], 2, []), + ] +) +def test_chunks(some_list, chunk_size, expected): + gen = code.chunks(some_list, chunk_size) + + assert list(gen) == expected + + +@pytest.mark.parametrize( + 'camel_cased_word, expected', + [ + ('BigGood', ['big', 'good']), + ('Big', ['big']), + ('bIg', ['b', 'ig']), + ] +) +def test_split_camel_case_words(camel_cased_word, expected): + assert code.split_camel_case_words(camel_cased_word) == expected + + +@pytest.mark.parametrize( + 'camel_cased_word, expected, ', + [('small', []), ('', [])] +) +def test_split_camel_case_words_with_exception(camel_cased_word, expected): + with pytest.raises(IndexError): + assert code.split_camel_case_words(camel_cased_word) == expected + + +@pytest.mark.parametrize( + 'url, expected', + [ + ('https://yandex.ru/', get_size()), + ('yandex.ru/', None), + ] +) +def test_get_image_height_in_pixels(url, expected, monkeypatch, test_image_factory): + monkeypatch.setattr(requests, 'get', b'some binary chars') + monkeypatch.setattr(Image, 'open', test_image_factory) + assert code.get_image_height_in_pixels(url) == expected + + +@pytest.mark.parametrize( + 'module, expected', + [ + ('a = "literal"\nb = 5', {'literal'}), + ('b = 5', set()), + ('a = "literal"\nb = "new literal"', {'literal', 'new literal'}), + ] +) +def test_extract_all_constants_from_ast(module, expected): + assert {*code.extract_all_constants_from_ast(ast.parse(module))} == expected + + +@pytest.mark.parametrize( + 'module, expected', + [ + ('def not_recursive(a, b, c):\n return a + b + c', False), + ('def recursive(a, b, c):\n return recursive(a, b, c)', True), + ] +) +def test_has_recursive_calls(module, expected): + assert code.has_recursive_calls(ast.parse(module).body[0]) == expected