Skip to content

Commit 5186ecd

Browse files
Chester Liclaude
andcommitted
gh-131178: Add tests for pyclbr command-line interface
Add CLI test coverage for `python -m pyclbr`, testing: - Running with a file path argument - Running with a module name argument - Running with no arguments (defaults to self-analysis) - Output line number verification - Error handling for nonexistent modules Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7817651 commit 5186ecd

File tree

2 files changed

+73
-0
lines changed

2 files changed

+73
-0
lines changed

Lib/test/test_pyclbr.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,20 @@
33
Nick Mathewson
44
'''
55

6+
import contextlib
67
import importlib.machinery
8+
import io
9+
import os
710
import sys
11+
import tempfile
812
from contextlib import contextmanager
913
from textwrap import dedent
1014
from types import FunctionType, MethodType, BuiltinFunctionType
1115
import pyclbr
1216
from unittest import TestCase, main as unittest_main
1317
from test.test_importlib import util as test_importlib_util
18+
from test.support import os_helper
19+
from unittest.mock import patch
1420

1521

1622
StaticMethodType = type(staticmethod(lambda: None))
@@ -284,5 +290,71 @@ def test_module_has_no_spec(self):
284290
pyclbr.readmodule_ex(module_name)
285291

286292

293+
class CommandLineTest(TestCase):
294+
295+
def setUp(self):
296+
self.filename = tempfile.mktemp(suffix='.py')
297+
self.addCleanup(os_helper.unlink, self.filename)
298+
299+
def _write_source(self, source):
300+
with open(self.filename, 'w') as f:
301+
f.write(dedent(source))
302+
303+
def _run_pyclbr(self, *args):
304+
"""Run pyclbr._main() with sys.argv patched, return stdout."""
305+
argv = ['pyclbr'] + list(args)
306+
output = io.StringIO()
307+
with patch.object(sys, 'argv', argv):
308+
with contextlib.redirect_stdout(output):
309+
pyclbr._main()
310+
return output.getvalue()
311+
312+
def test_file_path(self):
313+
self._write_source("""\
314+
class Spam:
315+
def eggs(self):
316+
pass
317+
def ham():
318+
pass
319+
""")
320+
out = self._run_pyclbr(self.filename)
321+
self.assertIn('class Spam', out)
322+
self.assertIn('def ham', out)
323+
# Nested method should be indented
324+
self.assertIn(' def eggs', out)
325+
326+
def test_module_name(self):
327+
out = self._run_pyclbr('pyclbr')
328+
self.assertIn('class Class', out)
329+
self.assertIn('class Function', out)
330+
self.assertIn('def readmodule_ex', out)
331+
332+
def test_default_no_args(self):
333+
# With no arguments, _main() defaults to analyzing pyclbr.py itself
334+
out = self._run_pyclbr()
335+
self.assertIn('class Class', out)
336+
self.assertIn('def readmodule_ex', out)
337+
338+
def test_output_line_numbers(self):
339+
self._write_source("""\
340+
def foo():
341+
pass
342+
class Bar:
343+
pass
344+
""")
345+
out = self._run_pyclbr(self.filename)
346+
# Each output line for a def/class should end with a line number
347+
for line in out.strip().splitlines():
348+
parts = line.split()
349+
self.assertTrue(
350+
parts[-1].isdigit(),
351+
f"Expected line number at end of: {line!r}"
352+
)
353+
354+
def test_nonexistent_module(self):
355+
with self.assertRaises(ModuleNotFoundError):
356+
self._run_pyclbr('nonexistent_xyz_module_12345')
357+
358+
287359
if __name__ == "__main__":
288360
unittest_main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add tests for :mod:`pyclbr` command-line interface.

0 commit comments

Comments
 (0)