1""" 2 test_pycode 3 ~~~~~~~~~~~ 4 5 Test pycode. 6 7 :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. 8 :license: BSD, see LICENSE for details. 9""" 10 11import os 12import sys 13 14import pytest 15 16import sphinx 17from sphinx.errors import PycodeError 18from sphinx.pycode import ModuleAnalyzer 19 20SPHINX_MODULE_PATH = os.path.splitext(sphinx.__file__)[0] + '.py' 21 22 23def test_ModuleAnalyzer_get_module_source(): 24 assert ModuleAnalyzer.get_module_source('sphinx') == (sphinx.__file__, sphinx.__loader__.get_source('sphinx')) 25 26 # failed to obtain source information from builtin modules 27 with pytest.raises(PycodeError): 28 ModuleAnalyzer.get_module_source('builtins') 29 with pytest.raises(PycodeError): 30 ModuleAnalyzer.get_module_source('itertools') 31 32 33def test_ModuleAnalyzer_for_string(): 34 analyzer = ModuleAnalyzer.for_string('print("Hello world")', 'module_name') 35 assert analyzer.modname == 'module_name' 36 assert analyzer.srcname == '<string>' 37 38 39def test_ModuleAnalyzer_for_file(): 40 analyzer = ModuleAnalyzer.for_string(SPHINX_MODULE_PATH, 'sphinx') 41 assert analyzer.modname == 'sphinx' 42 assert analyzer.srcname == '<string>' 43 44 45def test_ModuleAnalyzer_for_module(rootdir): 46 analyzer = ModuleAnalyzer.for_module('sphinx') 47 assert analyzer.modname == 'sphinx' 48 assert analyzer.srcname in (SPHINX_MODULE_PATH, 49 os.path.abspath(SPHINX_MODULE_PATH)) 50 51 path = rootdir / 'test-pycode' 52 sys.path.insert(0, path) 53 try: 54 analyzer = ModuleAnalyzer.for_module('cp_1251_coded') 55 docs = analyzer.find_attr_docs() 56 assert docs == {('', 'X'): ['It MUST look like X="\u0425"', '']} 57 finally: 58 sys.path.pop(0) 59 60 61def test_ModuleAnalyzer_for_file_in_egg(rootdir): 62 try: 63 path = rootdir / 'test-pycode-egg' / 'sample-0.0.0-py3.7.egg' 64 sys.path.insert(0, path) 65 66 import sample 67 analyzer = ModuleAnalyzer.for_file(sample.__file__, 'sample') 68 docs = analyzer.find_attr_docs() 69 assert docs == {('', 'CONSTANT'): ['constant on sample.py', '']} 70 finally: 71 sys.path.pop(0) 72 73 74def test_ModuleAnalyzer_for_module_in_egg(rootdir): 75 try: 76 path = rootdir / 'test-pycode-egg' / 'sample-0.0.0-py3.7.egg' 77 sys.path.insert(0, path) 78 79 analyzer = ModuleAnalyzer.for_module('sample') 80 docs = analyzer.find_attr_docs() 81 assert docs == {('', 'CONSTANT'): ['constant on sample.py', '']} 82 finally: 83 sys.path.pop(0) 84 85 86def test_ModuleAnalyzer_find_tags(): 87 code = ('class Foo(object):\n' # line: 1 88 ' """class Foo!"""\n' 89 ' def __init__(self):\n' 90 ' pass\n' 91 '\n' 92 ' def bar(self, arg1, arg2=True, *args, **kwargs):\n' 93 ' """method Foo.bar"""\n' 94 ' pass\n' 95 '\n' 96 ' class Baz(object):\n' 97 ' def __init__(self):\n' # line: 11 98 ' pass\n' 99 '\n' 100 'def qux():\n' 101 ' """function baz"""\n' 102 ' pass\n' 103 '\n' 104 '@decorator1\n' 105 '@decorator2\n' 106 'def quux():\n' 107 ' pass\n' # line: 21 108 '\n' 109 'class Corge(object):\n' 110 ' @decorator1\n' 111 ' @decorator2\n' 112 ' def grault(self):\n' 113 ' pass\n') 114 analyzer = ModuleAnalyzer.for_string(code, 'module') 115 tags = analyzer.find_tags() 116 assert set(tags.keys()) == {'Foo', 'Foo.__init__', 'Foo.bar', 117 'Foo.Baz', 'Foo.Baz.__init__', 'qux', 'quux', 118 'Corge', 'Corge.grault'} 119 assert tags['Foo'] == ('class', 1, 12) # type, start, end 120 assert tags['Foo.__init__'] == ('def', 3, 4) 121 assert tags['Foo.bar'] == ('def', 6, 8) 122 assert tags['Foo.Baz'] == ('class', 10, 12) 123 assert tags['Foo.Baz.__init__'] == ('def', 11, 12) 124 assert tags['qux'] == ('def', 14, 16) 125 assert tags['quux'] == ('def', 18, 21) 126 assert tags['Corge'] == ('class', 23, 27) 127 assert tags['Corge.grault'] == ('def', 24, 27) 128 129 130def test_ModuleAnalyzer_find_attr_docs(): 131 code = ('class Foo(object):\n' 132 ' """class Foo!"""\n' 133 ' #: comment before attr1\n' 134 ' attr1 = None\n' 135 ' attr2 = None # attribute comment for attr2 (without colon)\n' 136 ' attr3 = None #: attribute comment for attr3\n' 137 ' attr4 = None #: long attribute comment\n' 138 ' #: for attr4\n' 139 ' #: comment before attr5\n' 140 ' attr5 = None #: attribute comment for attr5\n' 141 ' attr6, attr7 = 1, 2 #: this comment is ignored\n' 142 '\n' 143 ' def __init__(self):\n' 144 ' self.attr8 = None #: first attribute comment (ignored)\n' 145 ' self.attr8 = None #: attribute comment for attr8\n' 146 ' #: comment before attr9\n' 147 ' self.attr9 = None #: comment after attr9\n' 148 ' "string after attr9"\n' 149 '\n' 150 ' def bar(self, arg1, arg2=True, *args, **kwargs):\n' 151 ' """method Foo.bar"""\n' 152 ' pass\n' 153 '\n' 154 'def baz():\n' 155 ' """function baz"""\n' 156 ' pass\n' 157 '\n' 158 'class Qux: attr1 = 1; attr2 = 2') 159 analyzer = ModuleAnalyzer.for_string(code, 'module') 160 docs = analyzer.find_attr_docs() 161 assert set(docs) == {('Foo', 'attr1'), 162 ('Foo', 'attr3'), 163 ('Foo', 'attr4'), 164 ('Foo', 'attr5'), 165 ('Foo', 'attr6'), 166 ('Foo', 'attr7'), 167 ('Foo', 'attr8'), 168 ('Foo', 'attr9')} 169 assert docs[('Foo', 'attr1')] == ['comment before attr1', ''] 170 assert docs[('Foo', 'attr3')] == ['attribute comment for attr3', ''] 171 assert docs[('Foo', 'attr4')] == ['long attribute comment', ''] 172 assert docs[('Foo', 'attr4')] == ['long attribute comment', ''] 173 assert docs[('Foo', 'attr5')] == ['attribute comment for attr5', ''] 174 assert docs[('Foo', 'attr6')] == ['this comment is ignored', ''] 175 assert docs[('Foo', 'attr7')] == ['this comment is ignored', ''] 176 assert docs[('Foo', 'attr8')] == ['attribute comment for attr8', ''] 177 assert docs[('Foo', 'attr9')] == ['string after attr9', ''] 178 assert analyzer.tagorder == {'Foo': 0, 179 'Foo.__init__': 8, 180 'Foo.attr1': 1, 181 'Foo.attr2': 2, 182 'Foo.attr3': 3, 183 'Foo.attr4': 4, 184 'Foo.attr5': 5, 185 'Foo.attr6': 6, 186 'Foo.attr7': 7, 187 'Foo.attr8': 10, 188 'Foo.attr9': 12, 189 'Foo.bar': 13, 190 'baz': 14, 191 'Qux': 15, 192 'Qux.attr1': 16, 193 'Qux.attr2': 17} 194