"""Tests for the FileProcessor class.""" import ast import tokenize from unittest import mock import pytest from flake8 import processor def test_read_lines_splits_lines(default_options): """Verify that read_lines splits the lines of the file.""" file_processor = processor.FileProcessor(__file__, default_options) lines = file_processor.lines assert len(lines) > 5 assert lines[0].strip() == '"""Tests for the FileProcessor class."""' def _lines_from_file(tmpdir, contents, options): f = tmpdir.join("f.py") # be careful to write the bytes exactly to avoid newline munging f.write_binary(contents) return processor.FileProcessor(f.strpath, options).lines def test_read_lines_universal_newlines(tmpdir, default_options): r"""Verify that line endings are translated to \n.""" lines = _lines_from_file( tmpdir, b"# coding: utf-8\r\nx = 1\r\n", default_options ) assert lines == ["# coding: utf-8\n", "x = 1\n"] def test_read_lines_incorrect_utf_16(tmpdir, default_options): """Verify that an incorrectly encoded file is read as latin-1.""" lines = _lines_from_file( tmpdir, b"# coding: utf16\nx = 1\n", default_options ) assert lines == ["# coding: utf16\n", "x = 1\n"] def test_read_lines_unknown_encoding(tmpdir, default_options): """Verify that an unknown encoding is still read as latin-1.""" lines = _lines_from_file( tmpdir, b"# coding: fake-encoding\nx = 1\n", default_options ) assert lines == ["# coding: fake-encoding\n", "x = 1\n"] @pytest.mark.parametrize( "first_line", [ '\xEF\xBB\xBF"""Module docstring."""\n', '\uFEFF"""Module docstring."""\n', ], ) def test_strip_utf_bom(first_line, default_options): r"""Verify that we strip '\xEF\xBB\xBF' from the first line.""" lines = [first_line] file_processor = processor.FileProcessor("-", default_options, lines[:]) assert file_processor.lines != lines assert file_processor.lines[0] == '"""Module docstring."""\n' @pytest.mark.parametrize( "lines, expected", [ (['\xEF\xBB\xBF"""Module docstring."""\n'], False), (['\uFEFF"""Module docstring."""\n'], False), (["#!/usr/bin/python", "# flake8 is great", "a = 1"], False), (["#!/usr/bin/python", "# flake8: noqa", "a = 1"], True), (["#!/usr/bin/python", "# flake8:noqa", "a = 1"], True), (["# flake8: noqa", "#!/usr/bin/python", "a = 1"], True), (["# flake8:noqa", "#!/usr/bin/python", "a = 1"], True), (["#!/usr/bin/python", "a = 1", "# flake8: noqa"], True), (["#!/usr/bin/python", "a = 1", "# flake8:noqa"], True), (["#!/usr/bin/python", "a = 1 # flake8: noqa"], False), (["#!/usr/bin/python", "a = 1 # flake8:noqa"], False), ], ) def test_should_ignore_file(lines, expected, default_options): """Verify that we ignore a file if told to.""" file_processor = processor.FileProcessor("-", default_options, lines) assert file_processor.should_ignore_file() is expected def test_should_ignore_file_to_handle_disable_noqa(default_options): """Verify that we ignore a file if told to.""" lines = ["# flake8: noqa"] file_processor = processor.FileProcessor("-", default_options, lines) assert file_processor.should_ignore_file() is True default_options.disable_noqa = True file_processor = processor.FileProcessor("-", default_options, lines) assert file_processor.should_ignore_file() is False @mock.patch("flake8.utils.stdin_get_value") def test_read_lines_from_stdin(stdin_get_value, default_options): """Verify that we use our own utility function to retrieve stdin.""" stdin_get_value.return_value = "" processor.FileProcessor("-", default_options) stdin_get_value.assert_called_once_with() @mock.patch("flake8.utils.stdin_get_value") def test_stdin_filename_attribute(stdin_get_value, default_options): """Verify that we update the filename attribute.""" stdin_get_value.return_value = "" file_processor = processor.FileProcessor("-", default_options) assert file_processor.filename == "stdin" @mock.patch("flake8.utils.stdin_get_value") def test_read_lines_uses_display_name(stdin_get_value, default_options): """Verify that when processing stdin we use a display name if present.""" default_options.stdin_display_name = "display_name.py" stdin_get_value.return_value = "" file_processor = processor.FileProcessor("-", default_options) assert file_processor.filename == "display_name.py" @mock.patch("flake8.utils.stdin_get_value") def test_read_lines_ignores_empty_display_name( stdin_get_value, default_options, ): """Verify that when processing stdin we use a display name if present.""" stdin_get_value.return_value = "" default_options.stdin_display_name = "" file_processor = processor.FileProcessor("-", default_options) assert file_processor.filename == "stdin" def test_noqa_line_for(default_options): """Verify we grab the correct line from the cached lines.""" file_processor = processor.FileProcessor( "-", default_options, lines=[ "Line 1\n", "Line 2\n", "Line 3\n", ], ) for i in range(1, 4): assert file_processor.noqa_line_for(i) == f"Line {i}\n" def test_noqa_line_for_continuation(default_options): """Verify that the correct "line" is retrieved for continuation.""" src = '''\ from foo \\ import bar # 2 x = """ hello world """ # 7 ''' lines = src.splitlines(True) file_processor = processor.FileProcessor("-", default_options, lines=lines) assert file_processor.noqa_line_for(0) is None l_1_2 = "from foo \\\n import bar # 2\n" assert file_processor.noqa_line_for(1) == l_1_2 assert file_processor.noqa_line_for(2) == l_1_2 assert file_processor.noqa_line_for(3) == "\n" l_4_7 = 'x = """\nhello\nworld\n""" # 7\n' for i in (4, 5, 6, 7): assert file_processor.noqa_line_for(i) == l_4_7 assert file_processor.noqa_line_for(8) is None def test_noqa_line_for_no_eol_at_end_of_file(default_options): """Verify that we properly handle noqa line at the end of the file.""" src = "from foo \\\nimport bar" # no end of file newline lines = src.splitlines(True) file_processor = processor.FileProcessor("-", default_options, lines=lines) l_1_2 = "from foo \\\nimport bar" assert file_processor.noqa_line_for(1) == l_1_2 assert file_processor.noqa_line_for(2) == l_1_2 def test_next_line(default_options): """Verify we update the file_processor state for each new line.""" file_processor = processor.FileProcessor( "-", default_options, lines=[ "Line 1", "Line 2", "Line 3", ], ) for i in range(1, 4): assert file_processor.next_line() == f"Line {i}" assert file_processor.line_number == i @pytest.mark.parametrize( "params, args, expected_kwargs", [ ( {"blank_before": True, "blank_lines": True}, None, {"blank_before": 0, "blank_lines": 0}, ), ( {"noqa": True, "fake": True}, {"fake": "foo"}, {"noqa": False, "fake": "foo"}, ), ( {"blank_before": True, "blank_lines": True, "noqa": True}, {"blank_before": 10, "blank_lines": 5, "noqa": True}, {"blank_before": 10, "blank_lines": 5, "noqa": True}, ), ({}, {"fake": "foo"}, {"fake": "foo"}), ({"non-existent": False}, {"fake": "foo"}, {"fake": "foo"}), ], ) def test_keyword_arguments_for(params, args, expected_kwargs, default_options): """Verify the keyword args are generated properly.""" file_processor = processor.FileProcessor( "-", default_options, lines=[ "Line 1", ], ) kwargs_for = file_processor.keyword_arguments_for assert kwargs_for(params, args) == expected_kwargs def test_keyword_arguments_for_does_not_handle_attribute_errors( default_options, ): """Verify we re-raise AttributeErrors.""" file_processor = processor.FileProcessor( "-", default_options, lines=[ "Line 1", ], ) with pytest.raises(AttributeError): file_processor.keyword_arguments_for({"fake": True}) @pytest.mark.parametrize( "unsplit_line, expected_lines", [ ("line", []), ("line 1\n", ["line 1"]), ("line 1\nline 2\n", ["line 1", "line 2"]), ("line 1\n\nline 2\n", ["line 1", "", "line 2"]), ], ) def test_split_line(unsplit_line, expected_lines, default_options): """Verify the token line splitting.""" file_processor = processor.FileProcessor( "-", default_options, lines=[ "Line 1", ], ) token = (1, unsplit_line, (0, 0), (0, 0), "") actual_lines = list(file_processor.split_line(token)) assert expected_lines == actual_lines assert len(actual_lines) == file_processor.line_number def test_build_ast(default_options): """Verify the logic for how we build an AST for plugins.""" file_processor = processor.FileProcessor( "-", default_options, lines=["a = 1\n"] ) module = file_processor.build_ast() assert isinstance(module, ast.Module) def test_next_logical_line_updates_the_previous_logical_line(default_options): """Verify that we update our tracking of the previous logical line.""" file_processor = processor.FileProcessor( "-", default_options, lines=["a = 1\n"] ) file_processor.indent_level = 1 file_processor.logical_line = "a = 1" assert file_processor.previous_logical == "" assert file_processor.previous_indent_level == 0 file_processor.next_logical_line() assert file_processor.previous_logical == "a = 1" assert file_processor.previous_indent_level == 1 def test_visited_new_blank_line(default_options): """Verify we update the number of blank lines seen.""" file_processor = processor.FileProcessor( "-", default_options, lines=["a = 1\n"] ) assert file_processor.blank_lines == 0 file_processor.visited_new_blank_line() assert file_processor.blank_lines == 1 def test_inside_multiline(default_options): """Verify we update the line number and reset multiline.""" file_processor = processor.FileProcessor( "-", default_options, lines=["a = 1\n"] ) assert file_processor.multiline is False assert file_processor.line_number == 0 with file_processor.inside_multiline(10): assert file_processor.multiline is True assert file_processor.line_number == 10 assert file_processor.multiline is False @pytest.mark.parametrize( "string, expected", [ ('""', '""'), ("''", "''"), ('"a"', '"x"'), ("'a'", "'x'"), ('"x"', '"x"'), ("'x'", "'x'"), ('"abcdef"', '"xxxxxx"'), ("'abcdef'", "'xxxxxx'"), ('""""""', '""""""'), ("''''''", "''''''"), ('"""a"""', '"""x"""'), ("'''a'''", "'''x'''"), ('"""x"""', '"""x"""'), ("'''x'''", "'''x'''"), ('"""abcdef"""', '"""xxxxxx"""'), ("'''abcdef'''", "'''xxxxxx'''"), ('"""xxxxxx"""', '"""xxxxxx"""'), ("'''xxxxxx'''", "'''xxxxxx'''"), ], ) def test_mutate_string(string, expected, default_options): """Verify we appropriately mutate the string to sanitize it.""" actual = processor.mutate_string(string) assert expected == actual @pytest.mark.parametrize( "string, expected", [ (" ", 4), (" ", 6), ("\t", 8), ("\t\t", 16), (" \t", 8), (" \t", 16), ], ) def test_expand_indent(string, expected): """Verify we correctly measure the amount of indentation.""" actual = processor.expand_indent(string) assert expected == actual @pytest.mark.parametrize( "token, log_string", [ [ ( tokenize.COMMENT, "# this is a comment", (1, 0), # (start_row, start_column) (1, 19), # (end_ro, end_column) "# this is a comment", ), "l.1\t[:19]\tCOMMENT\t'# this is a comment'", ], [ ( tokenize.COMMENT, "# this is a comment", (1, 5), # (start_row, start_column) (1, 19), # (end_ro, end_column) "# this is a comment", ), "l.1\t[5:19]\tCOMMENT\t'# this is a comment'", ], [ ( tokenize.COMMENT, "# this is a comment", (1, 0), # (start_row, start_column) (2, 19), # (end_ro, end_column) "# this is a comment", ), "l.1\tl.2\tCOMMENT\t'# this is a comment'", ], ], ) def test_log_token(token, log_string): """Verify we use the log object passed in.""" log = mock.Mock() processor.log_token(log, token) log.log.assert_called_once_with( 5, # flake8._EXTRA_VERBOSE log_string, ) @pytest.mark.parametrize( "current_count, token_text, expected", [ (0, "(", 1), (0, "[", 1), (0, "{", 1), (1, ")", 0), (1, "]", 0), (1, "}", 0), (10, "+", 10), ], ) def test_count_parentheses(current_count, token_text, expected): """Verify our arithmetic is correct.""" assert processor.count_parentheses(current_count, token_text) == expected def test_nonexistent_file(default_options): """Verify that FileProcessor raises IOError when a file does not exist.""" with pytest.raises(IOError): processor.FileProcessor("foobar.py", default_options)