1""" 2 test_apidoc 3 ~~~~~~~~~~~ 4 5 Test the sphinx.apidoc module. 6 7 :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. 8 :license: BSD, see LICENSE for details. 9""" 10 11from collections import namedtuple 12 13import pytest 14 15from sphinx.ext.apidoc import main as apidoc_main 16from sphinx.testing.path import path 17 18 19@pytest.fixture() 20def apidoc(rootdir, tempdir, apidoc_params): 21 _, kwargs = apidoc_params 22 coderoot = rootdir / kwargs.get('coderoot', 'test-root') 23 outdir = tempdir / 'out' 24 excludes = [coderoot / e for e in kwargs.get('excludes', [])] 25 args = ['-o', outdir, '-F', coderoot] + excludes + kwargs.get('options', []) 26 apidoc_main(args) 27 return namedtuple('apidoc', 'coderoot,outdir')(coderoot, outdir) 28 29 30@pytest.fixture 31def apidoc_params(request): 32 if hasattr(request.node, 'iter_markers'): # pytest-3.6.0 or newer 33 markers = request.node.iter_markers("apidoc") 34 else: 35 markers = request.node.get_marker("apidoc") 36 pargs = {} 37 kwargs = {} 38 39 if markers is not None: 40 for info in reversed(list(markers)): 41 for i, a in enumerate(info.args): 42 pargs[i] = a 43 kwargs.update(info.kwargs) 44 45 args = [pargs[i] for i in sorted(pargs.keys())] 46 return args, kwargs 47 48 49@pytest.mark.apidoc(coderoot='test-root') 50def test_simple(make_app, apidoc): 51 outdir = apidoc.outdir 52 assert (outdir / 'conf.py').isfile() 53 assert (outdir / 'index.rst').isfile() 54 55 app = make_app('text', srcdir=outdir) 56 app.build() 57 print(app._status.getvalue()) 58 print(app._warning.getvalue()) 59 60 61@pytest.mark.apidoc( 62 coderoot='test-apidoc-pep420/a', 63 options=["--implicit-namespaces"], 64) 65def test_pep_0420_enabled(make_app, apidoc): 66 outdir = apidoc.outdir 67 assert (outdir / 'conf.py').isfile() 68 assert (outdir / 'a.b.c.rst').isfile() 69 assert (outdir / 'a.b.e.rst').isfile() 70 assert (outdir / 'a.b.x.rst').isfile() 71 72 with open(outdir / 'a.b.c.rst') as f: 73 rst = f.read() 74 assert "automodule:: a.b.c.d\n" in rst 75 assert "automodule:: a.b.c\n" in rst 76 77 with open(outdir / 'a.b.e.rst') as f: 78 rst = f.read() 79 assert "automodule:: a.b.e.f\n" in rst 80 81 with open(outdir / 'a.b.x.rst') as f: 82 rst = f.read() 83 assert "automodule:: a.b.x.y\n" in rst 84 assert "automodule:: a.b.x\n" not in rst 85 86 app = make_app('text', srcdir=outdir) 87 app.build() 88 print(app._status.getvalue()) 89 print(app._warning.getvalue()) 90 91 builddir = outdir / '_build' / 'text' 92 assert (builddir / 'a.b.c.txt').isfile() 93 assert (builddir / 'a.b.e.txt').isfile() 94 assert (builddir / 'a.b.x.txt').isfile() 95 96 with open(builddir / 'a.b.c.txt') as f: 97 txt = f.read() 98 assert "a.b.c package\n" in txt 99 100 with open(builddir / 'a.b.e.txt') as f: 101 txt = f.read() 102 assert "a.b.e.f module\n" in txt 103 104 with open(builddir / 'a.b.x.txt') as f: 105 txt = f.read() 106 assert "a.b.x namespace\n" in txt 107 108 109@pytest.mark.apidoc( 110 coderoot='test-apidoc-pep420/a', 111 options=["--implicit-namespaces", "--separate"], 112) 113def test_pep_0420_enabled_separate(make_app, apidoc): 114 outdir = apidoc.outdir 115 assert (outdir / 'conf.py').isfile() 116 assert (outdir / 'a.b.c.rst').isfile() 117 assert (outdir / 'a.b.e.rst').isfile() 118 assert (outdir / 'a.b.e.f.rst').isfile() 119 assert (outdir / 'a.b.x.rst').isfile() 120 assert (outdir / 'a.b.x.y.rst').isfile() 121 122 with open(outdir / 'a.b.c.rst') as f: 123 rst = f.read() 124 assert ".. toctree::\n :maxdepth: 4\n\n a.b.c.d\n" in rst 125 126 with open(outdir / 'a.b.e.rst') as f: 127 rst = f.read() 128 assert ".. toctree::\n :maxdepth: 4\n\n a.b.e.f\n" in rst 129 130 with open(outdir / 'a.b.x.rst') as f: 131 rst = f.read() 132 assert ".. toctree::\n :maxdepth: 4\n\n a.b.x.y\n" in rst 133 134 app = make_app('text', srcdir=outdir) 135 app.build() 136 print(app._status.getvalue()) 137 print(app._warning.getvalue()) 138 139 builddir = outdir / '_build' / 'text' 140 assert (builddir / 'a.b.c.txt').isfile() 141 assert (builddir / 'a.b.e.txt').isfile() 142 assert (builddir / 'a.b.e.f.txt').isfile() 143 assert (builddir / 'a.b.x.txt').isfile() 144 assert (builddir / 'a.b.x.y.txt').isfile() 145 146 with open(builddir / 'a.b.c.txt') as f: 147 txt = f.read() 148 assert "a.b.c package\n" in txt 149 150 with open(builddir / 'a.b.e.f.txt') as f: 151 txt = f.read() 152 assert "a.b.e.f module\n" in txt 153 154 with open(builddir / 'a.b.x.txt') as f: 155 txt = f.read() 156 assert "a.b.x namespace\n" in txt 157 158 159@pytest.mark.apidoc(coderoot='test-apidoc-pep420/a') 160def test_pep_0420_disabled(make_app, apidoc): 161 outdir = apidoc.outdir 162 assert (outdir / 'conf.py').isfile() 163 assert not (outdir / 'a.b.c.rst').exists() 164 assert not (outdir / 'a.b.x.rst').exists() 165 166 app = make_app('text', srcdir=outdir) 167 app.build() 168 print(app._status.getvalue()) 169 print(app._warning.getvalue()) 170 171 172@pytest.mark.apidoc( 173 coderoot='test-apidoc-pep420/a/b') 174def test_pep_0420_disabled_top_level_verify(make_app, apidoc): 175 outdir = apidoc.outdir 176 assert (outdir / 'conf.py').isfile() 177 assert (outdir / 'c.rst').isfile() 178 assert not (outdir / 'x.rst').exists() 179 180 with open(outdir / 'c.rst') as f: 181 rst = f.read() 182 assert "c package\n" in rst 183 assert "automodule:: c.d\n" in rst 184 assert "automodule:: c\n" in rst 185 186 app = make_app('text', srcdir=outdir) 187 app.build() 188 print(app._status.getvalue()) 189 print(app._warning.getvalue()) 190 191 192@pytest.mark.apidoc( 193 coderoot='test-apidoc-trailing-underscore') 194def test_trailing_underscore(make_app, apidoc): 195 outdir = apidoc.outdir 196 assert (outdir / 'conf.py').isfile() 197 assert (outdir / 'package_.rst').isfile() 198 199 app = make_app('text', srcdir=outdir) 200 app.build() 201 print(app._status.getvalue()) 202 print(app._warning.getvalue()) 203 204 builddir = outdir / '_build' / 'text' 205 with open(builddir / 'package_.txt') as f: 206 rst = f.read() 207 assert "package_ package\n" in rst 208 assert "package_.module_ module\n" in rst 209 210 211@pytest.mark.apidoc( 212 coderoot='test-apidoc-pep420/a', 213 excludes=["b/c/d.py", "b/e/f.py", "b/e/__init__.py"], 214 options=["--implicit-namespaces", "--separate"], 215) 216def test_excludes(apidoc): 217 outdir = apidoc.outdir 218 assert (outdir / 'conf.py').isfile() 219 assert (outdir / 'a.rst').isfile() 220 assert (outdir / 'a.b.rst').isfile() 221 assert (outdir / 'a.b.c.rst').isfile() # generated because not empty 222 assert not (outdir / 'a.b.e.rst').isfile() # skipped because of empty after excludes 223 assert (outdir / 'a.b.x.rst').isfile() 224 assert (outdir / 'a.b.x.y.rst').isfile() 225 226 227@pytest.mark.apidoc( 228 coderoot='test-apidoc-pep420/a', 229 excludes=["b/e"], 230 options=["--implicit-namespaces", "--separate"], 231) 232def test_excludes_subpackage_should_be_skipped(apidoc): 233 """Subpackage exclusion should work.""" 234 outdir = apidoc.outdir 235 assert (outdir / 'conf.py').isfile() 236 assert (outdir / 'a.rst').isfile() 237 assert (outdir / 'a.b.rst').isfile() 238 assert (outdir / 'a.b.c.rst').isfile() # generated because not empty 239 assert not (outdir / 'a.b.e.f.rst').isfile() # skipped because 'b/e' subpackage is skipped 240 241 242@pytest.mark.apidoc( 243 coderoot='test-apidoc-pep420/a', 244 excludes=["b/e/f.py"], 245 options=["--implicit-namespaces", "--separate"], 246) 247def test_excludes_module_should_be_skipped(apidoc): 248 """Module exclusion should work.""" 249 outdir = apidoc.outdir 250 assert (outdir / 'conf.py').isfile() 251 assert (outdir / 'a.rst').isfile() 252 assert (outdir / 'a.b.rst').isfile() 253 assert (outdir / 'a.b.c.rst').isfile() # generated because not empty 254 assert not (outdir / 'a.b.e.f.rst').isfile() # skipped because of empty after excludes 255 256 257@pytest.mark.apidoc( 258 coderoot='test-apidoc-pep420/a', 259 excludes=[], 260 options=["--implicit-namespaces", "--separate"], 261) 262def test_excludes_module_should_not_be_skipped(apidoc): 263 """Module should be included if no excludes are used.""" 264 outdir = apidoc.outdir 265 assert (outdir / 'conf.py').isfile() 266 assert (outdir / 'a.rst').isfile() 267 assert (outdir / 'a.b.rst').isfile() 268 assert (outdir / 'a.b.c.rst').isfile() # generated because not empty 269 assert (outdir / 'a.b.e.f.rst').isfile() # skipped because of empty after excludes 270 271 272@pytest.mark.apidoc( 273 coderoot='test-root', 274 options=[ 275 '--doc-project', 'プロジェクト名', 276 '--doc-author', '著者名', 277 '--doc-version', 'バージョン', 278 '--doc-release', 'リリース', 279 ], 280) 281def test_multibyte_parameters(make_app, apidoc): 282 outdir = apidoc.outdir 283 assert (outdir / 'conf.py').isfile() 284 assert (outdir / 'index.rst').isfile() 285 286 conf_py = (outdir / 'conf.py').read_text() 287 assert "project = 'プロジェクト名'" in conf_py 288 assert "author = '著者名'" in conf_py 289 assert "version = 'バージョン'" in conf_py 290 assert "release = 'リリース'" in conf_py 291 292 app = make_app('text', srcdir=outdir) 293 app.build() 294 print(app._status.getvalue()) 295 print(app._warning.getvalue()) 296 297 298@pytest.mark.apidoc( 299 coderoot='test-root', 300 options=['--ext-mathjax'], 301) 302def test_extension_parsed(make_app, apidoc): 303 outdir = apidoc.outdir 304 assert (outdir / 'conf.py').isfile() 305 306 with open(outdir / 'conf.py') as f: 307 rst = f.read() 308 assert "sphinx.ext.mathjax" in rst 309 310 311@pytest.mark.apidoc( 312 coderoot='test-apidoc-toc/mypackage', 313 options=["--implicit-namespaces"], 314) 315def test_toc_all_references_should_exist_pep420_enabled(make_app, apidoc): 316 """All references in toc should exist. This test doesn't say if 317 directories with empty __init__.py and and nothing else should be 318 skipped, just ensures consistency between what's referenced in the toc 319 and what is created. This is the variant with pep420 enabled. 320 """ 321 outdir = apidoc.outdir 322 assert (outdir / 'conf.py').isfile() 323 324 toc = extract_toc(outdir / 'mypackage.rst') 325 326 refs = [l.strip() for l in toc.splitlines() if l.strip()] 327 found_refs = [] 328 missing_files = [] 329 for ref in refs: 330 if ref and ref[0] in (':', '#'): 331 continue 332 found_refs.append(ref) 333 filename = "{}.rst".format(ref) 334 if not (outdir / filename).isfile(): 335 missing_files.append(filename) 336 337 assert len(missing_files) == 0, \ 338 'File(s) referenced in TOC not found: {}\n' \ 339 'TOC:\n{}'.format(", ".join(missing_files), toc) 340 341 342@pytest.mark.apidoc( 343 coderoot='test-apidoc-toc/mypackage', 344) 345def test_toc_all_references_should_exist_pep420_disabled(make_app, apidoc): 346 """All references in toc should exist. This test doesn't say if 347 directories with empty __init__.py and and nothing else should be 348 skipped, just ensures consistency between what's referenced in the toc 349 and what is created. This is the variant with pep420 disabled. 350 """ 351 outdir = apidoc.outdir 352 assert (outdir / 'conf.py').isfile() 353 354 toc = extract_toc(outdir / 'mypackage.rst') 355 356 refs = [l.strip() for l in toc.splitlines() if l.strip()] 357 found_refs = [] 358 missing_files = [] 359 for ref in refs: 360 if ref and ref[0] in (':', '#'): 361 continue 362 filename = "{}.rst".format(ref) 363 found_refs.append(ref) 364 if not (outdir / filename).isfile(): 365 missing_files.append(filename) 366 367 assert len(missing_files) == 0, \ 368 'File(s) referenced in TOC not found: {}\n' \ 369 'TOC:\n{}'.format(", ".join(missing_files), toc) 370 371 372def extract_toc(path): 373 """Helper: Extract toc section from package rst file""" 374 with open(path) as f: 375 rst = f.read() 376 377 # Read out the part containing the toctree 378 toctree_start = "\n.. toctree::\n" 379 toctree_end = "\nSubmodules" 380 381 start_idx = rst.index(toctree_start) 382 end_idx = rst.index(toctree_end, start_idx) 383 toctree = rst[start_idx + len(toctree_start):end_idx] 384 385 return toctree 386 387 388@pytest.mark.apidoc( 389 coderoot='test-apidoc-subpackage-in-toc', 390 options=['--separate'] 391) 392def test_subpackage_in_toc(make_app, apidoc): 393 """Make sure that empty subpackages with non-empty subpackages in them 394 are not skipped (issue #4520) 395 """ 396 outdir = apidoc.outdir 397 assert (outdir / 'conf.py').isfile() 398 399 assert (outdir / 'parent.rst').isfile() 400 with open(outdir / 'parent.rst') as f: 401 parent = f.read() 402 assert 'parent.child' in parent 403 404 assert (outdir / 'parent.child.rst').isfile() 405 with open(outdir / 'parent.child.rst') as f: 406 parent_child = f.read() 407 assert 'parent.child.foo' in parent_child 408 409 assert (outdir / 'parent.child.foo.rst').isfile() 410 411 412def test_private(tempdir): 413 (tempdir / 'hello.py').write_text('') 414 (tempdir / '_world.py').write_text('') 415 416 # without --private option 417 apidoc_main(['-o', tempdir, tempdir]) 418 assert (tempdir / 'hello.rst').exists() 419 assert ':private-members:' not in (tempdir / 'hello.rst').read_text() 420 assert not (tempdir / '_world.rst').exists() 421 422 # with --private option 423 apidoc_main(['--private', '-f', '-o', tempdir, tempdir]) 424 assert (tempdir / 'hello.rst').exists() 425 assert ':private-members:' in (tempdir / 'hello.rst').read_text() 426 assert (tempdir / '_world.rst').exists() 427 428 429def test_toc_file(tempdir): 430 outdir = path(tempdir) 431 (outdir / 'module').makedirs() 432 (outdir / 'example.py').write_text('') 433 (outdir / 'module' / 'example.py').write_text('') 434 apidoc_main(['-o', tempdir, tempdir]) 435 assert (outdir / 'modules.rst').exists() 436 437 content = (outdir / 'modules.rst').read_text() 438 assert content == ("test_toc_file0\n" 439 "==============\n" 440 "\n" 441 ".. toctree::\n" 442 " :maxdepth: 4\n" 443 "\n" 444 " example\n") 445 446 447def test_module_file(tempdir): 448 outdir = path(tempdir) 449 (outdir / 'example.py').write_text('') 450 apidoc_main(['-o', tempdir, tempdir]) 451 assert (outdir / 'example.rst').exists() 452 453 content = (outdir / 'example.rst').read_text() 454 assert content == ("example module\n" 455 "==============\n" 456 "\n" 457 ".. automodule:: example\n" 458 " :members:\n" 459 " :undoc-members:\n" 460 " :show-inheritance:\n") 461 462 463def test_module_file_noheadings(tempdir): 464 outdir = path(tempdir) 465 (outdir / 'example.py').write_text('') 466 apidoc_main(['--no-headings', '-o', tempdir, tempdir]) 467 assert (outdir / 'example.rst').exists() 468 469 content = (outdir / 'example.rst').read_text() 470 assert content == (".. automodule:: example\n" 471 " :members:\n" 472 " :undoc-members:\n" 473 " :show-inheritance:\n") 474 475 476def test_package_file(tempdir): 477 outdir = path(tempdir) 478 (outdir / 'testpkg').makedirs() 479 (outdir / 'testpkg' / '__init__.py').write_text('') 480 (outdir / 'testpkg' / 'hello.py').write_text('') 481 (outdir / 'testpkg' / 'world.py').write_text('') 482 (outdir / 'testpkg' / 'subpkg').makedirs() 483 (outdir / 'testpkg' / 'subpkg' / '__init__.py').write_text('') 484 apidoc_main(['-o', tempdir, tempdir / 'testpkg']) 485 assert (outdir / 'testpkg.rst').exists() 486 assert (outdir / 'testpkg.subpkg.rst').exists() 487 488 content = (outdir / 'testpkg.rst').read_text() 489 assert content == ("testpkg package\n" 490 "===============\n" 491 "\n" 492 "Subpackages\n" 493 "-----------\n" 494 "\n" 495 ".. toctree::\n" 496 " :maxdepth: 4\n" 497 "\n" 498 " testpkg.subpkg\n" 499 "\n" 500 "Submodules\n" 501 "----------\n" 502 "\n" 503 "testpkg.hello module\n" 504 "--------------------\n" 505 "\n" 506 ".. automodule:: testpkg.hello\n" 507 " :members:\n" 508 " :undoc-members:\n" 509 " :show-inheritance:\n" 510 "\n" 511 "testpkg.world module\n" 512 "--------------------\n" 513 "\n" 514 ".. automodule:: testpkg.world\n" 515 " :members:\n" 516 " :undoc-members:\n" 517 " :show-inheritance:\n" 518 "\n" 519 "Module contents\n" 520 "---------------\n" 521 "\n" 522 ".. automodule:: testpkg\n" 523 " :members:\n" 524 " :undoc-members:\n" 525 " :show-inheritance:\n") 526 527 content = (outdir / 'testpkg.subpkg.rst').read_text() 528 assert content == ("testpkg.subpkg package\n" 529 "======================\n" 530 "\n" 531 "Module contents\n" 532 "---------------\n" 533 "\n" 534 ".. automodule:: testpkg.subpkg\n" 535 " :members:\n" 536 " :undoc-members:\n" 537 " :show-inheritance:\n") 538 539 540def test_package_file_separate(tempdir): 541 outdir = path(tempdir) 542 (outdir / 'testpkg').makedirs() 543 (outdir / 'testpkg' / '__init__.py').write_text('') 544 (outdir / 'testpkg' / 'example.py').write_text('') 545 apidoc_main(['--separate', '-o', tempdir, tempdir / 'testpkg']) 546 assert (outdir / 'testpkg.rst').exists() 547 assert (outdir / 'testpkg.example.rst').exists() 548 549 content = (outdir / 'testpkg.rst').read_text() 550 assert content == ("testpkg package\n" 551 "===============\n" 552 "\n" 553 "Submodules\n" 554 "----------\n" 555 "\n" 556 ".. toctree::\n" 557 " :maxdepth: 4\n" 558 "\n" 559 " testpkg.example\n" 560 "\n" 561 "Module contents\n" 562 "---------------\n" 563 "\n" 564 ".. automodule:: testpkg\n" 565 " :members:\n" 566 " :undoc-members:\n" 567 " :show-inheritance:\n") 568 569 content = (outdir / 'testpkg.example.rst').read_text() 570 assert content == ("testpkg.example module\n" 571 "======================\n" 572 "\n" 573 ".. automodule:: testpkg.example\n" 574 " :members:\n" 575 " :undoc-members:\n" 576 " :show-inheritance:\n") 577 578 579def test_package_file_module_first(tempdir): 580 outdir = path(tempdir) 581 (outdir / 'testpkg').makedirs() 582 (outdir / 'testpkg' / '__init__.py').write_text('') 583 (outdir / 'testpkg' / 'example.py').write_text('') 584 apidoc_main(['--module-first', '-o', tempdir, tempdir]) 585 586 content = (outdir / 'testpkg.rst').read_text() 587 assert content == ("testpkg package\n" 588 "===============\n" 589 "\n" 590 ".. automodule:: testpkg\n" 591 " :members:\n" 592 " :undoc-members:\n" 593 " :show-inheritance:\n" 594 "\n" 595 "Submodules\n" 596 "----------\n" 597 "\n" 598 "testpkg.example module\n" 599 "----------------------\n" 600 "\n" 601 ".. automodule:: testpkg.example\n" 602 " :members:\n" 603 " :undoc-members:\n" 604 " :show-inheritance:\n") 605 606 607def test_package_file_without_submodules(tempdir): 608 outdir = path(tempdir) 609 (outdir / 'testpkg').makedirs() 610 (outdir / 'testpkg' / '__init__.py').write_text('') 611 apidoc_main(['-o', tempdir, tempdir / 'testpkg']) 612 assert (outdir / 'testpkg.rst').exists() 613 614 content = (outdir / 'testpkg.rst').read_text() 615 assert content == ("testpkg package\n" 616 "===============\n" 617 "\n" 618 "Module contents\n" 619 "---------------\n" 620 "\n" 621 ".. automodule:: testpkg\n" 622 " :members:\n" 623 " :undoc-members:\n" 624 " :show-inheritance:\n") 625 626 627def test_namespace_package_file(tempdir): 628 outdir = path(tempdir) 629 (outdir / 'testpkg').makedirs() 630 (outdir / 'testpkg' / 'example.py').write_text('') 631 apidoc_main(['--implicit-namespace', '-o', tempdir, tempdir / 'testpkg']) 632 assert (outdir / 'testpkg.rst').exists() 633 634 content = (outdir / 'testpkg.rst').read_text() 635 assert content == ("testpkg namespace\n" 636 "=================\n" 637 "\n" 638 "Submodules\n" 639 "----------\n" 640 "\n" 641 "testpkg.example module\n" 642 "----------------------\n" 643 "\n" 644 ".. automodule:: testpkg.example\n" 645 " :members:\n" 646 " :undoc-members:\n" 647 " :show-inheritance:\n") 648