1"""Tests for the BaseFormatter object."""
2import argparse
3from unittest import mock
4
5import pytest
6
7from flake8 import style_guide
8from flake8.formatting import base
9
10
11def options(**kwargs):
12    """Create an argparse.Namespace instance."""
13    kwargs.setdefault("output_file", None)
14    kwargs.setdefault("tee", False)
15    return argparse.Namespace(**kwargs)
16
17
18@pytest.mark.parametrize("filename", [None, "out.txt"])
19def test_start(filename):
20    """Verify we open a new file in the start method."""
21    mock_open = mock.mock_open()
22    formatter = base.BaseFormatter(options(output_file=filename))
23    with mock.patch("flake8.formatting.base.open", mock_open):
24        formatter.start()
25
26    if filename is None:
27        assert mock_open.called is False
28    else:
29        mock_open.assert_called_once_with(filename, "a")
30
31
32def test_stop():
33    """Verify we close open file objects."""
34    filemock = mock.Mock()
35    formatter = base.BaseFormatter(options())
36    formatter.output_fd = filemock
37    formatter.stop()
38
39    filemock.close.assert_called_once_with()
40    assert formatter.output_fd is None
41
42
43def test_format_needs_to_be_implemented():
44    """Ensure BaseFormatter#format raises a NotImplementedError."""
45    formatter = base.BaseFormatter(options())
46    with pytest.raises(NotImplementedError):
47        formatter.format(
48            style_guide.Violation("A000", "file.py", 1, 1, "error text", None)
49        )
50
51
52def test_show_source_returns_nothing_when_not_showing_source():
53    """Ensure we return nothing when users want nothing."""
54    formatter = base.BaseFormatter(options(show_source=False))
55    assert (
56        formatter.show_source(
57            style_guide.Violation(
58                "A000", "file.py", 1, 1, "error text", "line"
59            )
60        )
61        == ""
62    )
63
64
65def test_show_source_returns_nothing_when_there_is_source():
66    """Ensure we return nothing when there is no line."""
67    formatter = base.BaseFormatter(options(show_source=True))
68    assert (
69        formatter.show_source(
70            style_guide.Violation("A000", "file.py", 1, 1, "error text", None)
71        )
72        == ""
73    )
74
75
76@pytest.mark.parametrize(
77    ("line1", "line2", "column"),
78    [
79        (
80            "x=1\n",
81            " ^",
82            2,
83        ),
84        (
85            "    x=(1\n       +2)\n",
86            "    ^",
87            5,
88        ),
89        (
90            "\tx\t=\ty\n",
91            "\t \t \t^",
92            6,
93        ),
94    ],
95)
96def test_show_source_updates_physical_line_appropriately(line1, line2, column):
97    """Ensure the error column is appropriately indicated."""
98    formatter = base.BaseFormatter(options(show_source=True))
99    error = style_guide.Violation("A000", "file.py", 1, column, "error", line1)
100    output = formatter.show_source(error)
101    assert output == line1 + line2
102
103
104@pytest.mark.parametrize("tee", [False, True])
105def test_write_uses_an_output_file(tee, capsys):
106    """Verify that we use the output file when it's present."""
107    line = "Something to write"
108    source = "source"
109    filemock = mock.Mock()
110
111    formatter = base.BaseFormatter(options(tee=tee))
112    formatter.output_fd = filemock
113
114    formatter.write(line, source)
115    if tee:
116        assert capsys.readouterr().out == f"{line}\n{source}\n"
117    else:
118        assert capsys.readouterr().out == ""
119
120    assert filemock.write.called is True
121    assert filemock.write.call_count == 2
122    assert filemock.write.mock_calls == [
123        mock.call(line + formatter.newline),
124        mock.call(source + formatter.newline),
125    ]
126
127
128def test_write_produces_stdout(capsys):
129    """Verify that we write to stdout without an output file."""
130    line = "Something to write"
131    source = "source"
132
133    formatter = base.BaseFormatter(options())
134    formatter.write(line, source)
135
136    assert capsys.readouterr().out == f"{line}\n{source}\n"
137
138
139class AfterInitFormatter(base.BaseFormatter):
140    """Subclass for testing after_init."""
141
142    def after_init(self):
143        """Define method to verify operation."""
144        self.post_initialized = True
145
146
147def test_after_init_is_always_called():
148    """Verify after_init is called."""
149    formatter = AfterInitFormatter(options())
150    assert formatter.post_initialized is True
151
152
153class FormatFormatter(base.BaseFormatter):
154    """Subclass for testing format."""
155
156    def format(self, error):
157        """Define method to verify operation."""
158        return repr(error)
159
160
161def test_handle_formats_the_error():
162    """Verify that a formatter will call format from handle."""
163    formatter = FormatFormatter(options(show_source=False))
164    filemock = formatter.output_fd = mock.Mock()
165    error = style_guide.Violation(
166        code="A001",
167        filename="example.py",
168        line_number=1,
169        column_number=1,
170        text="Fake error",
171        physical_line="a = 1",
172    )
173
174    formatter.handle(error)
175
176    filemock.write.assert_called_once_with(repr(error) + "\n")
177