"""Tests for flake8's utils module.""" import os import mock import pytest from flake8 import exceptions from flake8 import utils from flake8.plugins import manager as plugin_manager RELATIVE_PATHS = ["flake8", "pep8", "pyflakes", "mccabe"] @pytest.mark.parametrize("value,expected", [ ("E123,\n\tW234,\n E206", ["E123", "W234", "E206"]), ("E123,W234,E206", ["E123", "W234", "E206"]), ("E123 W234 E206", ["E123", "W234", "E206"]), ("E123\nW234 E206", ["E123", "W234", "E206"]), ("E123\nW234\nE206", ["E123", "W234", "E206"]), ("E123,W234,E206,", ["E123", "W234", "E206"]), ("E123,W234,E206, ,\n", ["E123", "W234", "E206"]), ("E123,W234,,E206,,", ["E123", "W234", "E206"]), ("E123, W234,, E206,,", ["E123", "W234", "E206"]), ("E123,,W234,,E206,,", ["E123", "W234", "E206"]), (["E123", "W234", "E206"], ["E123", "W234", "E206"]), (["E123", "\n\tW234", "\n E206"], ["E123", "W234", "E206"]), (["E123", "\n\tW234", "\n E206", "\n"], ["E123", "W234", "E206"]), (["E123", "\n\tW234", "", "\n E206", "\n"], ["E123", "W234", "E206"]), (["E123", "\n\tW234", "", "\n E206", ""], ["E123", "W234", "E206"]), ]) def test_parse_comma_separated_list(value, expected): """Verify that similar inputs produce identical outputs.""" assert utils.parse_comma_separated_list(value) == expected @pytest.mark.parametrize( ('value', 'expected'), ( # empty option configures nothing ('', []), (' ', []), ('\n\n\n', []), # basic case ( 'f.py:E123', [('f.py', ['E123'])], ), # multiple filenames, multiple codes ( 'f.py,g.py:E,F', [('f.py', ['E', 'F']), ('g.py', ['E', 'F'])], ), # demonstrate that whitespace is not important around tokens ( ' f.py , g.py : E , F ', [('f.py', ['E', 'F']), ('g.py', ['E', 'F'])], ), # whitespace can separate groups of configuration ( 'f.py:E g.py:F', [('f.py', ['E']), ('g.py', ['F'])], ), # newlines can separate groups of configuration ( 'f.py: E\ng.py: F\n', [('f.py', ['E']), ('g.py', ['F'])], ), # whitespace can be used in place of commas ( 'f.py g.py: E F', [('f.py', ['E', 'F']), ('g.py', ['E', 'F'])], ), # go ahead, indent your codes ( 'f.py:\n E,F\ng.py:\n G,H', [('f.py', ['E', 'F']), ('g.py', ['G', 'H'])], ), # capitalized filenames are ok too ( 'F.py,G.py: F,G', [('F.py', ['F', 'G']), ('G.py', ['F', 'G'])], ), # it's easier to allow zero filenames or zero codes than forbid it (':E', []), ('f.py:', []), (':E f.py:F', [('f.py', ['F'])]), ('f.py: g.py:F', [('g.py', ['F'])]), ('f.py:E:', []), ('f.py:E.py:', []), ('f.py:Eg.py:F', [('Eg.py', ['F'])]), # sequences are also valid (?) ( ['f.py:E,F', 'g.py:G,H'], [('f.py', ['E', 'F']), ('g.py', ['G', 'H'])], ), # six-digits codes are allowed ( 'f.py: ABC123', [('f.py', ['ABC123'])], ) ), ) def test_parse_files_to_codes_mapping(value, expected): """Test parsing of valid files-to-codes mappings.""" assert utils.parse_files_to_codes_mapping(value) == expected @pytest.mark.parametrize( 'value', ( # code while looking for filenames 'E123', 'f.py,E123', 'f.py E123', # eof while looking for filenames 'f.py', 'f.py:E,g.py' # colon while looking for codes 'f.py::', # no separator between 'f.py:E1F1', ), ) def test_invalid_file_list(value): """Test parsing of invalid files-to-codes mappings.""" with pytest.raises(exceptions.ExecutionError): utils.parse_files_to_codes_mapping(value) @pytest.mark.parametrize("value,expected", [ ("flake8", "flake8"), ("../flake8", os.path.abspath("../flake8")), ("flake8/", os.path.abspath("flake8")), ]) def test_normalize_path(value, expected): """Verify that we normalize paths provided to the tool.""" assert utils.normalize_path(value) == expected @pytest.mark.parametrize("value,expected", [ ("flake8,pep8,pyflakes,mccabe", ["flake8", "pep8", "pyflakes", "mccabe"]), ("flake8,\n\tpep8,\n pyflakes,\n\n mccabe", ["flake8", "pep8", "pyflakes", "mccabe"]), ("../flake8,../pep8,../pyflakes,../mccabe", [os.path.abspath("../" + p) for p in RELATIVE_PATHS]), ]) def test_normalize_paths(value, expected): """Verify we normalize comma-separated paths provided to the tool.""" assert utils.normalize_paths(value) == expected def test_is_windows_checks_for_nt(): """Verify that we correctly detect Windows.""" with mock.patch.object(os, 'name', 'nt'): assert utils.is_windows() is True with mock.patch.object(os, 'name', 'posix'): assert utils.is_windows() is False @pytest.mark.parametrize('filename,patterns,expected', [ ('foo.py', [], True), ('foo.py', ['*.pyc'], False), ('foo.pyc', ['*.pyc'], True), ('foo.pyc', ['*.swp', '*.pyc', '*.py'], True), ]) def test_fnmatch(filename, patterns, expected): """Verify that our fnmatch wrapper works as expected.""" assert utils.fnmatch(filename, patterns) is expected def test_filenames_from_a_directory(): """Verify that filenames_from walks a directory.""" filenames = list(utils.filenames_from('src/flake8/')) assert len(filenames) > 2 assert 'src/flake8/__init__.py' in filenames def test_filenames_from_a_directory_with_a_predicate(): """Verify that predicates filter filenames_from.""" filenames = list(utils.filenames_from( arg='src/flake8/', predicate=lambda filename: filename == 'flake8/__init__.py', )) assert len(filenames) > 2 assert 'flake8/__init__.py' not in filenames def test_filenames_from_a_directory_with_a_predicate_from_the_current_dir(): """Verify that predicates filter filenames_from.""" filenames = list(utils.filenames_from( arg='./src/flake8', predicate=lambda filename: filename == '__init__.py', )) assert len(filenames) > 2 assert './src/flake8/__init__.py' in filenames def test_filenames_from_a_single_file(): """Verify that we simply yield that filename.""" filenames = list(utils.filenames_from('flake8/__init__.py')) assert len(filenames) == 1 assert ['flake8/__init__.py'] == filenames def test_filenames_from_exclude_doesnt_exclude_directory_names(tmpdir): """Verify that we don't greedily exclude subdirs.""" tmpdir.join('1').ensure_dir().join('dont_return_me.py').ensure() tmpdir.join('2').join('1').ensure_dir().join('return_me.py').ensure() exclude = [tmpdir.join('1').strpath] # This acts similar to src.flake8.checker.is_path_excluded def predicate(pth): return utils.fnmatch(os.path.abspath(pth), exclude) with tmpdir.as_cwd(): filenames = list(utils.filenames_from('.', predicate)) assert filenames == [os.path.join('.', '2', '1', 'return_me.py')] def test_parameters_for_class_plugin(): """Verify that we can retrieve the parameters for a class plugin.""" class FakeCheck(object): def __init__(self, tree): pass plugin = plugin_manager.Plugin('plugin-name', object()) plugin._plugin = FakeCheck assert utils.parameters_for(plugin) == {'tree': True} def test_parameters_for_function_plugin(): """Verify that we retrieve the parameters for a function plugin.""" def fake_plugin(physical_line, self, tree, optional=None): pass plugin = plugin_manager.Plugin('plugin-name', object()) plugin._plugin = fake_plugin assert utils.parameters_for(plugin) == { 'physical_line': True, 'self': True, 'tree': True, 'optional': False, } def read_diff_file(filename): """Read the diff file in its entirety.""" with open(filename, 'r') as fd: content = fd.read() return content SINGLE_FILE_DIFF = read_diff_file('tests/fixtures/diffs/single_file_diff') SINGLE_FILE_INFO = { 'flake8/utils.py': set(range(75, 83)).union(set(range(84, 94))), } TWO_FILE_DIFF = read_diff_file('tests/fixtures/diffs/two_file_diff') TWO_FILE_INFO = { 'flake8/utils.py': set(range(75, 83)).union(set(range(84, 94))), 'tests/unit/test_utils.py': set(range(115, 128)), } MULTI_FILE_DIFF = read_diff_file('tests/fixtures/diffs/multi_file_diff') MULTI_FILE_INFO = { 'flake8/utils.py': set(range(75, 83)).union(set(range(84, 94))), 'tests/unit/test_utils.py': set(range(115, 129)), 'tests/fixtures/diffs/single_file_diff': set(range(1, 28)), 'tests/fixtures/diffs/two_file_diff': set(range(1, 46)), } @pytest.mark.parametrize("diff, parsed_diff", [ (SINGLE_FILE_DIFF, SINGLE_FILE_INFO), (TWO_FILE_DIFF, TWO_FILE_INFO), (MULTI_FILE_DIFF, MULTI_FILE_INFO), ]) def test_parse_unified_diff(diff, parsed_diff): """Verify that what we parse from a diff matches expectations.""" assert utils.parse_unified_diff(diff) == parsed_diff