bpo-19382: Adding test cases for module tabnanny (GH-851) · python/cpython@dfa9643 (original) (raw)

``

1

`` +

"""Testing tabnanny module.

``

``

2

+

``

3

`+

Glossary:

`

``

4

`+

`

``

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)

`