bpo-19382: Adding test cases for module tabnanny (GH-851) · python/cpython@dfa9643 (original) (raw)
``
1
`` +
"""Testing tabnanny
module.
``
``
2
+
``
3
`+
Glossary:
`
``
4
`+
- errored : Whitespace related problems present in file.
`
``
5
`+
"""
`
``
6
`+
from unittest import TestCase, mock
`
``
7
`+
from unittest import mock
`
``
8
`+
import tabnanny
`
``
9
`+
import tokenize
`
``
10
`+
import tempfile
`
``
11
`+
import textwrap
`
``
12
`+
from test.support import (captured_stderr, captured_stdout, script_helper,
`
``
13
`+
findfile, unlink)
`
``
14
+
``
15
+
``
16
`+
SOURCE_CODES = {
`
``
17
`+
"incomplete_expression": (
`
``
18
`+
'fruits = [\n'
`
``
19
`+
' "Apple",\n'
`
``
20
`+
' "Orange",\n'
`
``
21
`+
' "Banana",\n'
`
``
22
`+
'\n'
`
``
23
`+
'print(fruits)\n'
`
``
24
`+
),
`
``
25
`+
"wrong_indented": (
`
``
26
`+
'if True:\n'
`
``
27
`+
' print("hello")\n'
`
``
28
`+
' print("world")\n'
`
``
29
`+
'else:\n'
`
``
30
`+
' print("else called")\n'
`
``
31
`+
),
`
``
32
`+
"nannynag_errored": (
`
``
33
`+
'if True:\n'
`
``
34
`+
' \tprint("hello")\n'
`
``
35
`+
'\tprint("world")\n'
`
``
36
`+
'else:\n'
`
``
37
`+
' print("else called")\n'
`
``
38
`+
),
`
``
39
`+
"error_free": (
`
``
40
`+
'if True:\n'
`
``
41
`+
' print("hello")\n'
`
``
42
`+
' print("world")\n'
`
``
43
`+
'else:\n'
`
``
44
`+
' print("else called")\n'
`
``
45
`+
),
`
``
46
`+
"tab_space_errored_1": (
`
``
47
`+
'def my_func():\n'
`
``
48
`+
'\t print("hello world")\n'
`
``
49
`+
'\t if True:\n'
`
``
50
`+
'\t\tprint("If called")'
`
``
51
`+
),
`
``
52
`+
"tab_space_errored_2": (
`
``
53
`+
'def my_func():\n'
`
``
54
`+
'\t\tprint("Hello world")\n'
`
``
55
`+
'\t\tif True:\n'
`
``
56
`+
'\t print("If called")'
`
``
57
`+
)
`
``
58
`+
}
`
``
59
+
``
60
+
``
61
`+
class TemporaryPyFile:
`
``
62
`+
"""Create a temporary python source code file."""
`
``
63
+
``
64
`+
def init(self, source_code='', directory=None):
`
``
65
`+
self.source_code = source_code
`
``
66
`+
self.dir = directory
`
``
67
+
``
68
`+
def enter(self):
`
``
69
`+
with tempfile.NamedTemporaryFile(
`
``
70
`+
mode='w', dir=self.dir, suffix=".py", delete=False
`
``
71
`+
) as f:
`
``
72
`+
f.write(self.source_code)
`
``
73
`+
self.file_path = f.name
`
``
74
`+
return self.file_path
`
``
75
+
``
76
`+
def exit(self, exc_type, exc_value, exc_traceback):
`
``
77
`+
unlink(self.file_path)
`
``
78
+
``
79
+
``
80
`+
class TestFormatWitnesses(TestCase):
`
``
81
`` +
"""Testing tabnanny.format_witnesses()
."""
``
``
82
+
``
83
`+
def test_format_witnesses(self):
`
``
84
`+
"""Asserting formatter result by giving various input samples."""
`
``
85
`+
tests = [
`
``
86
`+
('Test', 'at tab sizes T, e, s, t'),
`
``
87
`+
('', 'at tab size '),
`
``
88
`+
('t', 'at tab size t'),
`
``
89
`+
(' t ', 'at tab sizes , , t, , '),
`
``
90
`+
]
`
``
91
+
``
92
`+
for words, expected in tests:
`
``
93
`+
with self.subTest(words=words, expected=expected):
`
``
94
`+
self.assertEqual(tabnanny.format_witnesses(words), expected)
`
``
95
+
``
96
+
``
97
`+
class TestErrPrint(TestCase):
`
``
98
`` +
"""Testing tabnanny.errprint()
."""
``
``
99
+
``
100
`+
def test_errprint(self):
`
``
101
`` +
"""Asserting result of tabnanny.errprint()
by giving sample inputs."""
``
``
102
`+
tests = [
`
``
103
`+
(['first', 'second'], 'first second\n'),
`
``
104
`+
(['first'], 'first\n'),
`
``
105
`+
([1, 2, 3], '1 2 3\n'),
`
``
106
`+
([], '\n')
`
``
107
`+
]
`
``
108
+
``
109
`+
for args, expected in tests:
`
``
110
`+
with self.subTest(arguments=args, expected=expected):
`
``
111
`+
with captured_stderr() as stderr:
`
``
112
`+
tabnanny.errprint(*args)
`
``
113
`+
self.assertEqual(stderr.getvalue() , expected)
`
``
114
+
``
115
+
``
116
`+
class TestNannyNag(TestCase):
`
``
117
`+
def test_all_methods(self):
`
``
118
`` +
"""Asserting behaviour of tabnanny.NannyNag
exception."""
``
``
119
`+
tests = [
`
``
120
`+
(
`
``
121
`+
tabnanny.NannyNag(0, "foo", "bar"),
`
``
122
`+
{'lineno': 0, 'msg': 'foo', 'line': 'bar'}
`
``
123
`+
),
`
``
124
`+
(
`
``
125
`+
tabnanny.NannyNag(5, "testmsg", "testline"),
`
``
126
`+
{'lineno': 5, 'msg': 'testmsg', 'line': 'testline'}
`
``
127
`+
)
`
``
128
`+
]
`
``
129
`+
for nanny, expected in tests:
`
``
130
`+
line_number = nanny.get_lineno()
`
``
131
`+
msg = nanny.get_msg()
`
``
132
`+
line = nanny.get_line()
`
``
133
`+
with self.subTest(
`
``
134
`+
line_number=line_number, expected=expected['lineno']
`
``
135
`+
):
`
``
136
`+
self.assertEqual(expected['lineno'], line_number)
`
``
137
`+
with self.subTest(msg=msg, expected=expected['msg']):
`
``
138
`+
self.assertEqual(expected['msg'], msg)
`
``
139
`+
with self.subTest(line=line, expected=expected['line']):
`
``
140
`+
self.assertEqual(expected['line'], line)
`
``
141
+
``
142
+
``
143
`+
class TestCheck(TestCase):
`
``
144
`+
"""Testing tabnanny.check()."""
`
``
145
+
``
146
`+
def setUp(self):
`
``
147
`+
self.addCleanup(setattr, tabnanny, 'verbose', tabnanny.verbose)
`
``
148
`+
tabnanny.verbose = 0 # Forcefully deactivating verbose mode.
`
``
149
+
``
150
`+
def verify_tabnanny_check(self, dir_or_file, out="", err=""):
`
``
151
`+
"""Common verification for tabnanny.check().
`
``
152
+
``
153
`` +
Use this method to assert expected values of stdout
and stderr
after
``
``
154
`` +
running tabnanny.check() on given dir
or file
path. Because
``
``
155
`` +
tabnanny.check() captures exceptions and writes to stdout
and
``
``
156
`` +
stderr
, asserting standard outputs is the only way.
``
``
157
`+
"""
`
``
158
`+
with captured_stdout() as stdout, captured_stderr() as stderr:
`
``
159
`+
tabnanny.check(dir_or_file)
`
``
160
`+
self.assertEqual(stdout.getvalue(), out)
`
``
161
`+
self.assertEqual(stderr.getvalue(), err)
`
``
162
+
``
163
`+
def test_correct_file(self):
`
``
164
`+
"""A python source code file without any errors."""
`
``
165
`+
with TemporaryPyFile(SOURCE_CODES["error_free"]) as file_path:
`
``
166
`+
self.verify_tabnanny_check(file_path)
`
``
167
+
``
168
`+
def test_correct_directory_verbose(self):
`
``
169
`+
"""Directory containing few error free python source code files.
`
``
170
+
``
171
`` +
Because order of files returned by os.lsdir()
is not fixed, verify the
``
``
172
`` +
existence of each output lines at stdout
using in
operator.
``
``
173
`` +
verbose
mode of tabnanny.verbose
asserts stdout
.
``
``
174
`+
"""
`
``
175
`+
with tempfile.TemporaryDirectory() as tmp_dir:
`
``
176
`+
lines = [f"{tmp_dir!r}: listing directory\n",]
`
``
177
`+
file1 = TemporaryPyFile(SOURCE_CODES["error_free"], directory=tmp_dir)
`
``
178
`+
file2 = TemporaryPyFile(SOURCE_CODES["error_free"], directory=tmp_dir)
`
``
179
`+
with file1 as file1_path, file2 as file2_path:
`
``
180
`+
for file_path in (file1_path, file2_path):
`
``
181
`+
lines.append(f"{file_path!r}: Clean bill of health.\n")
`
``
182
+
``
183
`+
tabnanny.verbose = 1
`
``
184
`+
with captured_stdout() as stdout, captured_stderr() as stderr:
`
``
185
`+
tabnanny.check(tmp_dir)
`
``
186
`+
stdout = stdout.getvalue()
`
``
187
`+
for line in lines:
`
``
188
`+
with self.subTest(line=line):
`
``
189
`+
self.assertIn(line, stdout)
`
``
190
`+
self.assertEqual(stderr.getvalue(), "")
`
``
191
+
``
192
`+
def test_correct_directory(self):
`
``
193
`+
"""Directory which contains few error free python source code files."""
`
``
194
`+
with tempfile.TemporaryDirectory() as tmp_dir:
`
``
195
`+
with TemporaryPyFile(SOURCE_CODES["error_free"], directory=tmp_dir):
`
``
196
`+
self.verify_tabnanny_check(tmp_dir)
`
``
197
+
``
198
`+
def test_when_wrong_indented(self):
`
``
199
`` +
"""A python source code file eligible for raising IndentationError
."""
``
``
200
`+
with TemporaryPyFile(SOURCE_CODES["wrong_indented"]) as file_path:
`
``
201
`+
err = ('unindent does not match any outer indentation level'
`
``
202
`+
' (, line 3)\n')
`
``
203
`+
err = f"{file_path!r}: Indentation Error: {err}"
`
``
204
`+
self.verify_tabnanny_check(file_path, err=err)
`
``
205
+
``
206
`+
def test_when_tokenize_tokenerror(self):
`
``
207
`+
"""A python source code file eligible for raising 'tokenize.TokenError'."""
`
``
208
`+
with TemporaryPyFile(SOURCE_CODES["incomplete_expression"]) as file_path:
`
``
209
`+
err = "('EOF in multi-line statement', (7, 0))\n"
`
``
210
`+
err = f"{file_path!r}: Token Error: {err}"
`
``
211
`+
self.verify_tabnanny_check(file_path, err=err)
`
``
212
+
``
213
`+
def test_when_nannynag_error_verbose(self):
`
``
214
`` +
"""A python source code file eligible for raising tabnanny.NannyNag
.
``
``
215
+
``
216
`` +
Tests will assert stdout
after activating tabnanny.verbose
mode.
``
``
217
`+
"""
`
``
218
`+
with TemporaryPyFile(SOURCE_CODES["nannynag_errored"]) as file_path:
`
``
219
`+
out = f"{file_path!r}: *** Line 3: trouble in tab city! ***\n"
`
``
220
`+
out += "offending line: '\tprint("world")\n'\n"
`
``
221
`+
out += "indent not equal e.g. at tab size 1\n"
`
``
222
+
``
223
`+
tabnanny.verbose = 1
`
``
224
`+
self.verify_tabnanny_check(file_path, out=out)
`
``
225
+
``
226
`+
def test_when_nannynag_error(self):
`
``
227
`` +
"""A python source code file eligible for raising tabnanny.NannyNag
."""
``
``
228
`+
with TemporaryPyFile(SOURCE_CODES["nannynag_errored"]) as file_path:
`
``
229
`+
out = f"{file_path} 3 '\tprint("world")\n'\n"
`
``
230
`+
self.verify_tabnanny_check(file_path, out=out)
`
``
231
+
``
232
`+
def test_when_no_file(self):
`
``
233
`+
"""A python file which does not exist actually in system."""
`
``
234
`+
path = 'no_file.py'
`
``
235
`+
err = f"{path!r}: I/O Error: [Errno 2] No such file or directory: {path!r}\n"
`
``
236
`+
self.verify_tabnanny_check(path, err=err)
`
``
237
+
``
238
`+
def test_errored_directory(self):
`
``
239
`+
"""Directory containing wrongly indented python source code files."""
`
``
240
`+
with tempfile.TemporaryDirectory() as tmp_dir:
`
``
241
`+
error_file = TemporaryPyFile(
`
``
242
`+
SOURCE_CODES["wrong_indented"], directory=tmp_dir
`
``
243
`+
)
`
``
244
`+
code_file = TemporaryPyFile(
`
``
245
`+
SOURCE_CODES["error_free"], directory=tmp_dir
`
``
246
`+
)
`
``
247
`+
with error_file as e_file, code_file as c_file:
`
``
248
`+
err = ('unindent does not match any outer indentation level'
`
``
249
`+
' (, line 3)\n')
`
``
250
`+
err = f"{e_file!r}: Indentation Error: {err}"
`
``
251
`+
self.verify_tabnanny_check(tmp_dir, err=err)
`
``
252
+
``
253
+
``
254
`+
class TestProcessTokens(TestCase):
`
``
255
`` +
"""Testing tabnanny.process_tokens()
."""
``
``
256
+
``
257
`+
@mock.patch('tabnanny.NannyNag')
`
``
258
`+
def test_with_correct_code(self, MockNannyNag):
`
``
259
`+
"""A python source code without any whitespace related problems."""
`
``
260
+
``
261
`+
with TemporaryPyFile(SOURCE_CODES["error_free"]) as file_path:
`
``
262
`+
with open(file_path) as f:
`
``
263
`+
tabnanny.process_tokens(tokenize.generate_tokens(f.readline))
`
``
264
`+
self.assertFalse(MockNannyNag.called)
`
``
265
+
``
266
`+
def test_with_errored_codes_samples(self):
`
``
267
`+
"""A python source code with whitespace related sampled problems."""
`
``
268
+
``
269
`+
"tab_space_errored_1": executes block under type == tokenize.INDENT
`
``
270
`` +
at tabnanny.process_tokens()
.
``
``
271
`+
"tab space_errored_2": executes block under
`
``
272
`` +
check_equal and type not in JUNK
condition at
``
``
273
`` +
tabnanny.process_tokens()
.
``
``
274
+
``
275
`+
for key in ["tab_space_errored_1", "tab_space_errored_2"]:
`
``
276
`+
with self.subTest(key=key):
`
``
277
`+
with TemporaryPyFile(SOURCE_CODES[key]) as file_path:
`
``
278
`+
with open(file_path) as f:
`
``
279
`+
tokens = tokenize.generate_tokens(f.readline)
`
``
280
`+
with self.assertRaises(tabnanny.NannyNag):
`
``
281
`+
tabnanny.process_tokens(tokens)
`
``
282
+
``
283
+
``
284
`+
class TestCommandLine(TestCase):
`
``
285
`` +
"""Tests command line interface of tabnanny
."""
``
``
286
+
``
287
`+
def validate_cmd(self, *args, stdout="", stderr="", partial=False):
`
``
288
`+
"""Common function to assert the behaviour of command line interface."""
`
``
289
`+
_, out, err = script_helper.assert_python_ok('-m', 'tabnanny', *args)
`
``
290
`` +
Note: The splitlines()
will solve the problem of CRLF(\r) added
``
``
291
`+
by OS Windows.
`
``
292
`+
out = out.decode('ascii')
`
``
293
`+
err = err.decode('ascii')
`
``
294
`+
if partial:
`
``
295
`+
for std, output in ((stdout, out), (stderr, err)):
`
``
296
`+
_output = output.splitlines()
`
``
297
`+
for _std in std.splitlines():
`
``
298
`+
with self.subTest(std=_std, output=_output):
`
``
299
`+
self.assertIn(_std, _output)
`
``
300
`+
else:
`
``
301
`+
self.assertListEqual(out.splitlines(), stdout.splitlines())
`
``
302
`+
self.assertListEqual(err.splitlines(), stderr.splitlines())
`
``
303
+
``
304
`+
def test_with_errored_file(self):
`
``
305
`+
"""Should displays error when errored python file is given."""
`
``
306
`+
with TemporaryPyFile(SOURCE_CODES["wrong_indented"]) as file_path:
`
``
307
`+
stderr = f"{file_path!r}: Indentation Error: "
`
``
308
`+
stderr += ('unindent does not match any outer indentation level'
`
``
309
`+
' (, line 3)')
`
``
310
`+
self.validate_cmd(file_path, stderr=stderr)
`
``
311
+
``
312
`+
def test_with_error_free_file(self):
`
``
313
`+
"""Should not display anything if python file is correctly indented."""
`
``
314
`+
with TemporaryPyFile(SOURCE_CODES["error_free"]) as file_path:
`
``
315
`+
self.validate_cmd(file_path)
`
``
316
+
``
317
`+
def test_command_usage(self):
`
``
318
`+
"""Should display usage on no arguments."""
`
``
319
`+
path = findfile('tabnanny.py')
`
``
320
`+
stderr = f"Usage: {path} [-v] file_or_directory ..."
`
``
321
`+
self.validate_cmd(stderr=stderr)
`
``
322
+
``
323
`+
def test_quiet_flag(self):
`
``
324
`+
"""Should display less when quite mode is on."""
`
``
325
`+
with TemporaryPyFile(SOURCE_CODES["nannynag_errored"]) as file_path:
`
``
326
`+
stdout = f"{file_path}\n"
`
``
327
`+
self.validate_cmd("-q", file_path, stdout=stdout)
`
``
328
+
``
329
`+
def test_verbose_mode(self):
`
``
330
`+
"""Should display more error information if verbose mode is on."""
`
``
331
`+
with TemporaryPyFile(SOURCE_CODES["nannynag_errored"]) as path:
`
``
332
`+
stdout = textwrap.dedent(
`
``
333
`+
"offending line: '\tprint("world")\n'"
`
``
334
`+
).strip()
`
``
335
`+
self.validate_cmd("-v", path, stdout=stdout, partial=True)
`
``
336
+
``
337
`+
def test_double_verbose_mode(self):
`
``
338
`+
"""Should display detailed error information if double verbose is on."""
`
``
339
`+
with TemporaryPyFile(SOURCE_CODES["nannynag_errored"]) as path:
`
``
340
`+
stdout = textwrap.dedent(
`
``
341
`+
"offending line: '\tprint("world")\n'"
`
``
342
`+
).strip()
`
``
343
`+
self.validate_cmd("-vv", path, stdout=stdout, partial=True)
`