1# Copyright 2015 The TensorFlow Authors. All Rights Reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14# ============================================================================== 15"""Tests for documentation parser.""" 16 17from __future__ import absolute_import 18from __future__ import division 19from __future__ import print_function 20 21import collections 22import functools 23import os 24import sys 25 26from tensorflow.python.platform import googletest 27from tensorflow.python.util import tf_inspect 28from tensorflow.tools.docs import doc_controls 29from tensorflow.tools.docs import parser 30 31# The test needs a real module. `types.ModuleType()` doesn't work, as the result 32# is a `builtin` module. Using "parser" here is arbitraty. The tests don't 33# depend on the module contents. At this point in the process the public api 34# has already been extracted. 35test_module = parser 36 37 38def test_function(unused_arg, unused_kwarg='default'): 39 """Docstring for test function.""" 40 pass 41 42 43def test_function_with_args_kwargs(unused_arg, *unused_args, **unused_kwargs): 44 """Docstring for second test function.""" 45 pass 46 47 48class ParentClass(object): 49 50 @doc_controls.do_not_doc_inheritable 51 def hidden_method(self): 52 pass 53 54 55class TestClass(ParentClass): 56 """Docstring for TestClass itself.""" 57 58 def a_method(self, arg='default'): 59 """Docstring for a method.""" 60 pass 61 62 def hidden_method(self): 63 pass 64 65 @doc_controls.do_not_generate_docs 66 def hidden_method2(self): 67 pass 68 69 class ChildClass(object): 70 """Docstring for a child class.""" 71 pass 72 73 @property 74 def a_property(self): 75 """Docstring for a property.""" 76 pass 77 78 CLASS_MEMBER = 'a class member' 79 80 81class DummyVisitor(object): 82 83 def __init__(self, index, duplicate_of): 84 self.index = index 85 self.duplicate_of = duplicate_of 86 87 88class ParserTest(googletest.TestCase): 89 90 def test_documentation_path(self): 91 self.assertEqual('test.md', parser.documentation_path('test')) 92 self.assertEqual('test/module.md', parser.documentation_path('test.module')) 93 94 def test_replace_references(self): 95 class HasOneMember(object): 96 97 def foo(self): 98 pass 99 100 string = ( 101 'A @{tf.reference}, another @{tf.reference$with\nnewline}, a member ' 102 '@{tf.reference.foo}, and a @{tf.third$link `text` with `code` in ' 103 'it}.') 104 duplicate_of = {'tf.third': 'tf.fourth'} 105 index = {'tf.reference': HasOneMember, 106 'tf.reference.foo': HasOneMember.foo, 107 'tf.third': HasOneMember, 108 'tf.fourth': HasOneMember} 109 110 visitor = DummyVisitor(index, duplicate_of) 111 112 reference_resolver = parser.ReferenceResolver.from_visitor( 113 visitor=visitor, doc_index={}, py_module_names=['tf']) 114 115 result = reference_resolver.replace_references(string, '../..') 116 self.assertEqual('A <a href="../../tf/reference.md">' 117 '<code>tf.reference</code></a>, ' 118 'another <a href="../../tf/reference.md">' 119 'with\nnewline</a>, ' 120 'a member <a href="../../tf/reference.md#foo">' 121 '<code>tf.reference.foo</code></a>, ' 122 'and a <a href="../../tf/fourth.md">link ' 123 '<code>text</code> with ' 124 '<code>code</code> in it</a>.', result) 125 126 def test_doc_replace_references(self): 127 string = '@{$doc1} @{$doc1#abc} @{$doc1$link} @{$doc1#def$zelda} @{$do/c2}' 128 129 class DocInfo(object): 130 pass 131 doc1 = DocInfo() 132 doc1.title = 'Title1' 133 doc1.url = 'URL1' 134 doc2 = DocInfo() 135 doc2.title = 'Two words' 136 doc2.url = 'somewhere/else' 137 doc_index = {'doc1': doc1, 'do/c2': doc2} 138 139 visitor = DummyVisitor(index={}, duplicate_of={}) 140 141 reference_resolver = parser.ReferenceResolver.from_visitor( 142 visitor=visitor, doc_index=doc_index, py_module_names=['tf']) 143 result = reference_resolver.replace_references(string, 'python') 144 self.assertEqual('<a href="../URL1">Title1</a> ' 145 '<a href="../URL1#abc">Title1</a> ' 146 '<a href="../URL1">link</a> ' 147 '<a href="../URL1#def">zelda</a> ' 148 '<a href="../somewhere/else">Two words</a>', result) 149 150 def test_docs_for_class(self): 151 152 index = { 153 'TestClass': TestClass, 154 'TestClass.a_method': TestClass.a_method, 155 'TestClass.a_property': TestClass.a_property, 156 'TestClass.ChildClass': TestClass.ChildClass, 157 'TestClass.CLASS_MEMBER': TestClass.CLASS_MEMBER 158 } 159 160 visitor = DummyVisitor(index=index, duplicate_of={}) 161 162 reference_resolver = parser.ReferenceResolver.from_visitor( 163 visitor=visitor, doc_index={}, py_module_names=['tf']) 164 165 tree = { 166 'TestClass': ['a_method', 'a_property', 'ChildClass', 'CLASS_MEMBER'] 167 } 168 parser_config = parser.ParserConfig( 169 reference_resolver=reference_resolver, 170 duplicates={}, 171 duplicate_of={}, 172 tree=tree, 173 index=index, 174 reverse_index={}, 175 guide_index={}, 176 base_dir='/') 177 178 page_info = parser.docs_for_object( 179 full_name='TestClass', py_object=TestClass, parser_config=parser_config) 180 181 # Make sure the brief docstring is present 182 self.assertEqual( 183 tf_inspect.getdoc(TestClass).split('\n')[0], page_info.doc.brief) 184 185 # Make sure the method is present 186 self.assertEqual(TestClass.a_method, page_info.methods[0].obj) 187 188 # Make sure that the signature is extracted properly and omits self. 189 self.assertEqual(["arg='default'"], page_info.methods[0].signature) 190 191 # Make sure the property is present 192 self.assertIs(TestClass.a_property, page_info.properties[0].obj) 193 194 # Make sure there is a link to the child class and it points the right way. 195 self.assertIs(TestClass.ChildClass, page_info.classes[0].obj) 196 197 # Make sure this file is contained as the definition location. 198 self.assertEqual(os.path.relpath(__file__, '/'), page_info.defined_in.path) 199 200 def test_namedtuple_field_order(self): 201 namedtupleclass = collections.namedtuple('namedtupleclass', 202 {'z', 'y', 'x', 'w', 'v', 'u'}) 203 204 index = { 205 'namedtupleclass': namedtupleclass, 206 'namedtupleclass.u': namedtupleclass.u, 207 'namedtupleclass.v': namedtupleclass.v, 208 'namedtupleclass.w': namedtupleclass.w, 209 'namedtupleclass.x': namedtupleclass.x, 210 'namedtupleclass.y': namedtupleclass.y, 211 'namedtupleclass.z': namedtupleclass.z, 212 } 213 214 visitor = DummyVisitor(index=index, duplicate_of={}) 215 216 reference_resolver = parser.ReferenceResolver.from_visitor( 217 visitor=visitor, doc_index={}, py_module_names=['tf']) 218 219 tree = {'namedtupleclass': {'u', 'v', 'w', 'x', 'y', 'z'}} 220 parser_config = parser.ParserConfig( 221 reference_resolver=reference_resolver, 222 duplicates={}, 223 duplicate_of={}, 224 tree=tree, 225 index=index, 226 reverse_index={}, 227 guide_index={}, 228 base_dir='/') 229 230 page_info = parser.docs_for_object( 231 full_name='namedtupleclass', 232 py_object=namedtupleclass, 233 parser_config=parser_config) 234 235 # Each namedtiple field has a docstring of the form: 236 # 'Alias for field number ##'. These props are returned sorted. 237 238 def sort_key(prop_info): 239 return int(prop_info.obj.__doc__.split(' ')[-1]) 240 241 self.assertSequenceEqual(page_info.properties, 242 sorted(page_info.properties, key=sort_key)) 243 244 def test_docs_for_class_should_skip(self): 245 246 class Parent(object): 247 248 @doc_controls.do_not_doc_inheritable 249 def a_method(self, arg='default'): 250 pass 251 252 class Child(Parent): 253 254 def a_method(self, arg='default'): 255 pass 256 257 index = { 258 'Child': Child, 259 'Child.a_method': Child.a_method, 260 } 261 262 visitor = DummyVisitor(index=index, duplicate_of={}) 263 264 reference_resolver = parser.ReferenceResolver.from_visitor( 265 visitor=visitor, doc_index={}, py_module_names=['tf']) 266 267 tree = { 268 'Child': ['a_method'], 269 } 270 271 parser_config = parser.ParserConfig( 272 reference_resolver=reference_resolver, 273 duplicates={}, 274 duplicate_of={}, 275 tree=tree, 276 index=index, 277 reverse_index={}, 278 guide_index={}, 279 base_dir='/') 280 281 page_info = parser.docs_for_object( 282 full_name='Child', py_object=Child, parser_config=parser_config) 283 284 # Make sure the `a_method` is not present 285 self.assertEqual(0, len(page_info.methods)) 286 287 def test_docs_for_message_class(self): 288 289 class CMessage(object): 290 291 def hidden(self): 292 pass 293 294 class Message(object): 295 296 def hidden2(self): 297 pass 298 299 class MessageMeta(object): 300 301 def hidden3(self): 302 pass 303 304 class ChildMessage(CMessage, Message, MessageMeta): 305 306 def my_method(self): 307 pass 308 309 index = { 310 'ChildMessage': ChildMessage, 311 'ChildMessage.hidden': ChildMessage.hidden, 312 'ChildMessage.hidden2': ChildMessage.hidden2, 313 'ChildMessage.hidden3': ChildMessage.hidden3, 314 'ChildMessage.my_method': ChildMessage.my_method, 315 } 316 317 visitor = DummyVisitor(index=index, duplicate_of={}) 318 319 reference_resolver = parser.ReferenceResolver.from_visitor( 320 visitor=visitor, doc_index={}, py_module_names=['tf']) 321 322 tree = {'ChildMessage': ['hidden', 'hidden2', 'hidden3', 'my_method']} 323 324 parser_config = parser.ParserConfig( 325 reference_resolver=reference_resolver, 326 duplicates={}, 327 duplicate_of={}, 328 tree=tree, 329 index=index, 330 reverse_index={}, 331 guide_index={}, 332 base_dir='/') 333 334 page_info = parser.docs_for_object( 335 full_name='ChildMessage', 336 py_object=ChildMessage, 337 parser_config=parser_config) 338 339 self.assertEqual(1, len(page_info.methods)) 340 self.assertEqual('my_method', page_info.methods[0].short_name) 341 342 def test_docs_for_module(self): 343 344 index = { 345 'TestModule': 346 test_module, 347 'TestModule.test_function': 348 test_function, 349 'TestModule.test_function_with_args_kwargs': 350 test_function_with_args_kwargs, 351 'TestModule.TestClass': 352 TestClass, 353 } 354 355 visitor = DummyVisitor(index=index, duplicate_of={}) 356 357 reference_resolver = parser.ReferenceResolver.from_visitor( 358 visitor=visitor, doc_index={}, py_module_names=['tf']) 359 360 tree = { 361 'TestModule': ['TestClass', 'test_function', 362 'test_function_with_args_kwargs'] 363 } 364 parser_config = parser.ParserConfig( 365 reference_resolver=reference_resolver, 366 duplicates={}, 367 duplicate_of={}, 368 tree=tree, 369 index=index, 370 reverse_index={}, 371 guide_index={}, 372 base_dir='/') 373 374 page_info = parser.docs_for_object( 375 full_name='TestModule', 376 py_object=test_module, 377 parser_config=parser_config) 378 379 # Make sure the brief docstring is present 380 self.assertEqual( 381 tf_inspect.getdoc(test_module).split('\n')[0], page_info.doc.brief) 382 383 # Make sure that the members are there 384 funcs = {f_info.obj for f_info in page_info.functions} 385 self.assertEqual({test_function, test_function_with_args_kwargs}, funcs) 386 387 classes = {cls_info.obj for cls_info in page_info.classes} 388 self.assertEqual({TestClass}, classes) 389 390 # Make sure the module's file is contained as the definition location. 391 self.assertEqual( 392 os.path.relpath(test_module.__file__, '/'), page_info.defined_in.path) 393 394 def test_docs_for_function(self): 395 index = { 396 'test_function': test_function 397 } 398 399 visitor = DummyVisitor(index=index, duplicate_of={}) 400 401 reference_resolver = parser.ReferenceResolver.from_visitor( 402 visitor=visitor, doc_index={}, py_module_names=['tf']) 403 404 tree = { 405 '': ['test_function'] 406 } 407 parser_config = parser.ParserConfig( 408 reference_resolver=reference_resolver, 409 duplicates={}, 410 duplicate_of={}, 411 tree=tree, 412 index=index, 413 reverse_index={}, 414 guide_index={}, 415 base_dir='/') 416 417 page_info = parser.docs_for_object( 418 full_name='test_function', 419 py_object=test_function, 420 parser_config=parser_config) 421 422 # Make sure the brief docstring is present 423 self.assertEqual( 424 tf_inspect.getdoc(test_function).split('\n')[0], page_info.doc.brief) 425 426 # Make sure the extracted signature is good. 427 self.assertEqual(['unused_arg', "unused_kwarg='default'"], 428 page_info.signature) 429 430 # Make sure this file is contained as the definition location. 431 self.assertEqual(os.path.relpath(__file__, '/'), page_info.defined_in.path) 432 433 def test_docs_for_function_with_kwargs(self): 434 index = { 435 'test_function_with_args_kwargs': test_function_with_args_kwargs 436 } 437 438 visitor = DummyVisitor(index=index, duplicate_of={}) 439 440 reference_resolver = parser.ReferenceResolver.from_visitor( 441 visitor=visitor, doc_index={}, py_module_names=['tf']) 442 443 tree = { 444 '': ['test_function_with_args_kwargs'] 445 } 446 parser_config = parser.ParserConfig( 447 reference_resolver=reference_resolver, 448 duplicates={}, 449 duplicate_of={}, 450 tree=tree, 451 index=index, 452 reverse_index={}, 453 guide_index={}, 454 base_dir='/') 455 456 page_info = parser.docs_for_object( 457 full_name='test_function_with_args_kwargs', 458 py_object=test_function_with_args_kwargs, 459 parser_config=parser_config) 460 461 # Make sure the brief docstring is present 462 self.assertEqual( 463 tf_inspect.getdoc(test_function_with_args_kwargs).split('\n')[0], 464 page_info.doc.brief) 465 466 # Make sure the extracted signature is good. 467 self.assertEqual(['unused_arg', '*unused_args', '**unused_kwargs'], 468 page_info.signature) 469 470 def test_parse_md_docstring(self): 471 472 def test_function_with_fancy_docstring(arg): 473 """Function with a fancy docstring. 474 475 And a bunch of references: @{tf.reference}, another @{tf.reference}, 476 a member @{tf.reference.foo}, and a @{tf.third}. 477 478 Args: 479 arg: An argument. 480 481 Raises: 482 an exception 483 484 Returns: 485 arg: the input, and 486 arg: the input, again. 487 488 @compatibility(numpy) 489 NumPy has nothing as awesome as this function. 490 @end_compatibility 491 492 @compatibility(theano) 493 Theano has nothing as awesome as this function. 494 495 Check it out. 496 @end_compatibility 497 498 """ 499 return arg, arg 500 501 class HasOneMember(object): 502 503 def foo(self): 504 pass 505 506 duplicate_of = {'tf.third': 'tf.fourth'} 507 index = { 508 'tf': test_module, 509 'tf.fancy': test_function_with_fancy_docstring, 510 'tf.reference': HasOneMember, 511 'tf.reference.foo': HasOneMember.foo, 512 'tf.third': HasOneMember, 513 'tf.fourth': HasOneMember 514 } 515 516 visitor = DummyVisitor(index=index, duplicate_of=duplicate_of) 517 518 reference_resolver = parser.ReferenceResolver.from_visitor( 519 visitor=visitor, doc_index={}, py_module_names=['tf']) 520 521 doc_info = parser._parse_md_docstring(test_function_with_fancy_docstring, 522 '../..', reference_resolver) 523 524 self.assertNotIn('@', doc_info.docstring) 525 self.assertNotIn('compatibility', doc_info.docstring) 526 self.assertNotIn('Raises:', doc_info.docstring) 527 528 self.assertEqual(len(doc_info.function_details), 3) 529 self.assertEqual(set(doc_info.compatibility.keys()), {'numpy', 'theano'}) 530 531 self.assertEqual(doc_info.compatibility['numpy'], 532 'NumPy has nothing as awesome as this function.\n') 533 534 def test_generate_index(self): 535 536 index = { 537 'tf': test_module, 538 'tf.TestModule': test_module, 539 'tf.test_function': test_function, 540 'tf.TestModule.test_function': test_function, 541 'tf.TestModule.TestClass': TestClass, 542 'tf.TestModule.TestClass.a_method': TestClass.a_method, 543 'tf.TestModule.TestClass.a_property': TestClass.a_property, 544 'tf.TestModule.TestClass.ChildClass': TestClass.ChildClass, 545 } 546 duplicate_of = {'tf.TestModule.test_function': 'tf.test_function'} 547 548 visitor = DummyVisitor(index=index, duplicate_of=duplicate_of) 549 550 reference_resolver = parser.ReferenceResolver.from_visitor( 551 visitor=visitor, doc_index={}, py_module_names=['tf']) 552 553 docs = parser.generate_global_index('TestLibrary', index=index, 554 reference_resolver=reference_resolver) 555 556 # Make sure duplicates and non-top-level symbols are in the index, but 557 # methods and properties are not. 558 self.assertNotIn('a_method', docs) 559 self.assertNotIn('a_property', docs) 560 self.assertIn('TestModule.TestClass', docs) 561 self.assertIn('TestModule.TestClass.ChildClass', docs) 562 self.assertIn('TestModule.test_function', docs) 563 # Leading backtick to make sure it's included top-level. 564 # This depends on formatting, but should be stable. 565 self.assertIn('<code>tf.test_function', docs) 566 567 def test_argspec_for_functools_partial(self): 568 # pylint: disable=unused-argument 569 def test_function_for_partial1(arg1, arg2, kwarg1=1, kwarg2=2): 570 pass 571 572 def test_function_for_partial2(arg1, arg2, *my_args, **my_kwargs): 573 pass 574 # pylint: enable=unused-argument 575 576 # pylint: disable=protected-access 577 # Make sure everything works for regular functions. 578 expected = tf_inspect.FullArgSpec( 579 args=['arg1', 'arg2', 'kwarg1', 'kwarg2'], 580 varargs=None, 581 varkw=None, 582 defaults=(1, 2), 583 kwonlyargs=[], 584 kwonlydefaults=None, 585 annotations={}) 586 self.assertEqual(expected, parser._get_arg_spec(test_function_for_partial1)) 587 588 # Make sure doing nothing works. 589 expected = tf_inspect.FullArgSpec( 590 args=['arg1', 'arg2', 'kwarg1', 'kwarg2'], 591 varargs=None, 592 varkw=None, 593 defaults=(1, 2), 594 kwonlyargs=[], 595 kwonlydefaults=None, 596 annotations={}) 597 partial = functools.partial(test_function_for_partial1) 598 self.assertEqual(expected, parser._get_arg_spec(partial)) 599 600 # Make sure setting args from the front works. 601 expected = tf_inspect.FullArgSpec( 602 args=['arg2', 'kwarg1', 'kwarg2'], 603 varargs=None, 604 varkw=None, 605 defaults=(1, 2), 606 kwonlyargs=[], 607 kwonlydefaults=None, 608 annotations={}) 609 partial = functools.partial(test_function_for_partial1, 1) 610 self.assertEqual(expected, parser._get_arg_spec(partial)) 611 612 expected = tf_inspect.FullArgSpec( 613 args=['kwarg2'], 614 varargs=None, 615 varkw=None, 616 defaults=(2,), 617 kwonlyargs=[], 618 kwonlydefaults=None, 619 annotations={}) 620 partial = functools.partial(test_function_for_partial1, 1, 2, 3) 621 self.assertEqual(expected, parser._get_arg_spec(partial)) 622 623 # Make sure setting kwargs works. 624 expected = tf_inspect.FullArgSpec( 625 args=['arg1', 'arg2', 'kwarg2'], 626 varargs=None, 627 varkw=None, 628 defaults=(2,), 629 kwonlyargs=[], 630 kwonlydefaults=None, 631 annotations={}) 632 partial = functools.partial(test_function_for_partial1, kwarg1=0) 633 self.assertEqual(expected, parser._get_arg_spec(partial)) 634 635 expected = tf_inspect.FullArgSpec( 636 args=['arg1', 'arg2', 'kwarg1'], 637 varargs=None, 638 varkw=None, 639 defaults=(1,), 640 kwonlyargs=[], 641 kwonlydefaults=None, 642 annotations={}) 643 partial = functools.partial(test_function_for_partial1, kwarg2=0) 644 self.assertEqual(expected, parser._get_arg_spec(partial)) 645 646 expected = tf_inspect.FullArgSpec( 647 args=['arg1'], 648 varargs=None, 649 varkw=None, 650 defaults=(), 651 kwonlyargs=[], 652 kwonlydefaults=None, 653 annotations={}) 654 partial = functools.partial(test_function_for_partial1, 655 arg2=0, kwarg1=0, kwarg2=0) 656 self.assertEqual(expected, parser._get_arg_spec(partial)) 657 658 # Make sure *args, *kwargs is accounted for. 659 expected = tf_inspect.FullArgSpec( 660 args=[], 661 varargs='my_args', 662 varkw='my_kwargs', 663 defaults=(), 664 kwonlyargs=[], 665 kwonlydefaults=None, 666 annotations={}) 667 partial = functools.partial(test_function_for_partial2, 0, 1) 668 self.assertEqual(expected, parser._get_arg_spec(partial)) 669 670 # pylint: enable=protected-access 671 672 def testSaveReferenceResolver(self): 673 you_cant_serialize_this = object() 674 675 duplicate_of = {'AClass': ['AClass2']} 676 doc_index = {'doc': you_cant_serialize_this} 677 is_fragment = { 678 'tf': False, 679 'tf.VERSION': True, 680 'tf.AClass': False, 681 'tf.AClass.method': True, 682 'tf.AClass2': False, 683 'tf.function': False 684 } 685 py_module_names = ['tf', 'tfdbg'] 686 687 resolver = parser.ReferenceResolver(duplicate_of, doc_index, is_fragment, 688 py_module_names) 689 690 outdir = googletest.GetTempDir() 691 692 filepath = os.path.join(outdir, 'resolver.json') 693 694 resolver.to_json_file(filepath) 695 resolver2 = parser.ReferenceResolver.from_json_file(filepath, doc_index) 696 697 # There are no __slots__, so all fields are visible in __dict__. 698 self.assertEqual(resolver.__dict__, resolver2.__dict__) 699 700 def testIsFreeFunction(self): 701 702 result = parser.is_free_function(test_function, 'test_module.test_function', 703 {'test_module': test_module}) 704 self.assertTrue(result) 705 706 result = parser.is_free_function(test_function, 'TestClass.test_function', 707 {'TestClass': TestClass}) 708 self.assertFalse(result) 709 710 result = parser.is_free_function(TestClass, 'TestClass', {}) 711 self.assertFalse(result) 712 713 result = parser.is_free_function(test_module, 'test_module', {}) 714 self.assertFalse(result) 715 716 717RELU_DOC = """Computes rectified linear: `max(features, 0)` 718 719Args: 720 features: A `Tensor`. Must be one of the following types: `float32`, 721 `float64`, `int32`, `int64`, `uint8`, `int16`, `int8`, `uint16`, 722 `half`. 723 name: A name for the operation (optional) 724 725Returns: 726 A `Tensor`. Has the same type as `features` 727""" 728 729 730class TestParseFunctionDetails(googletest.TestCase): 731 732 def test_parse_function_details(self): 733 docstring, function_details = parser._parse_function_details(RELU_DOC) 734 735 self.assertEqual(len(function_details), 2) 736 args = function_details[0] 737 self.assertEqual(args.keyword, 'Args') 738 self.assertEqual(len(args.header), 0) 739 self.assertEqual(len(args.items), 2) 740 self.assertEqual(args.items[0][0], 'features') 741 self.assertEqual(args.items[1][0], 'name') 742 self.assertEqual(args.items[1][1], 743 'A name for the operation (optional)\n\n') 744 returns = function_details[1] 745 self.assertEqual(returns.keyword, 'Returns') 746 747 relu_doc_lines = RELU_DOC.split('\n') 748 self.assertEqual(docstring, relu_doc_lines[0] + '\n\n') 749 self.assertEqual(returns.header, relu_doc_lines[-2] + '\n') 750 751 self.assertEqual( 752 RELU_DOC, 753 docstring + ''.join(str(detail) for detail in function_details)) 754 755 756class TestGenerateSignature(googletest.TestCase): 757 758 def test_known_object(self): 759 known_object = object() 760 reverse_index = {id(known_object): 'location.of.object.in.api'} 761 762 def example_fun(arg=known_object): # pylint: disable=unused-argument 763 pass 764 765 sig = parser._generate_signature(example_fun, reverse_index) 766 self.assertEqual(sig, ['arg=location.of.object.in.api']) 767 768 def test_literals(self): 769 if sys.version_info >= (3, 0): 770 print('Warning: Doc generation is not supported from python3.') 771 return 772 773 def example_fun(a=5, b=5.0, c=None, d=True, e='hello', f=(1, (2, 3))): # pylint: disable=g-bad-name, unused-argument 774 pass 775 776 sig = parser._generate_signature(example_fun, reverse_index={}) 777 self.assertEqual( 778 sig, ['a=5', 'b=5.0', 'c=None', 'd=True', "e='hello'", 'f=(1, (2, 3))']) 779 780 def test_dotted_name(self): 781 if sys.version_info >= (3, 0): 782 print('Warning: Doc generation is not supported from python3.') 783 return 784 785 # pylint: disable=g-bad-name 786 class a(object): 787 788 class b(object): 789 790 class c(object): 791 792 class d(object): 793 794 def __init__(self, *args): 795 pass 796 # pylint: enable=g-bad-name 797 798 e = {'f': 1} 799 800 def example_fun(arg1=a.b.c.d, arg2=a.b.c.d(1, 2), arg3=e['f']): # pylint: disable=unused-argument 801 pass 802 803 sig = parser._generate_signature(example_fun, reverse_index={}) 804 self.assertEqual(sig, ['arg1=a.b.c.d', 'arg2=a.b.c.d(1, 2)', "arg3=e['f']"]) 805 806if __name__ == '__main__': 807 googletest.main() 808