|
3 | 3 | Nick Mathewson |
4 | 4 | ''' |
5 | 5 |
|
| 6 | +import contextlib |
6 | 7 | import importlib.machinery |
| 8 | +import io |
| 9 | +import os |
7 | 10 | import sys |
| 11 | +import tempfile |
8 | 12 | from contextlib import contextmanager |
9 | 13 | from textwrap import dedent |
10 | 14 | from types import FunctionType, MethodType, BuiltinFunctionType |
11 | 15 | import pyclbr |
12 | 16 | from unittest import TestCase, main as unittest_main |
13 | 17 | from test.test_importlib import util as test_importlib_util |
| 18 | +from test.support import os_helper |
| 19 | +from unittest.mock import patch |
14 | 20 |
|
15 | 21 |
|
16 | 22 | StaticMethodType = type(staticmethod(lambda: None)) |
@@ -284,5 +290,71 @@ def test_module_has_no_spec(self): |
284 | 290 | pyclbr.readmodule_ex(module_name) |
285 | 291 |
|
286 | 292 |
|
| 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 | + |
287 | 359 | if __name__ == "__main__": |
288 | 360 | unittest_main() |
0 commit comments