# Copyright (C) 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Tests for the helptext module.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function import os import sys import textwrap from fire import formatting from fire import helptext from fire import test_components as tc from fire import testutils from fire import trace import six class HelpTest(testutils.BaseTestCase): def setUp(self): super(HelpTest, self).setUp() os.environ['ANSI_COLORS_DISABLED'] = '1' def testHelpTextNoDefaults(self): component = tc.NoDefaults help_screen = helptext.HelpText( component=component, trace=trace.FireTrace(component, name='NoDefaults')) self.assertIn('NAME\n NoDefaults', help_screen) self.assertIn('SYNOPSIS\n NoDefaults', help_screen) self.assertNotIn('DESCRIPTION', help_screen) self.assertNotIn('NOTES', help_screen) def testHelpTextNoDefaultsObject(self): component = tc.NoDefaults() help_screen = helptext.HelpText( component=component, trace=trace.FireTrace(component, name='NoDefaults')) self.assertIn('NAME\n NoDefaults', help_screen) self.assertIn('SYNOPSIS\n NoDefaults COMMAND', help_screen) self.assertNotIn('DESCRIPTION', help_screen) self.assertIn('COMMANDS\n COMMAND is one of the following:', help_screen) self.assertIn('double', help_screen) self.assertIn('triple', help_screen) self.assertNotIn('NOTES', help_screen) def testHelpTextFunction(self): component = tc.NoDefaults().double help_screen = helptext.HelpText( component=component, trace=trace.FireTrace(component, name='double')) self.assertIn('NAME\n double', help_screen) self.assertIn('SYNOPSIS\n double COUNT', help_screen) self.assertNotIn('DESCRIPTION', help_screen) self.assertIn('POSITIONAL ARGUMENTS\n COUNT', help_screen) self.assertIn( 'NOTES\n You can also use flags syntax for POSITIONAL ARGUMENTS', help_screen) def testHelpTextFunctionWithDefaults(self): component = tc.WithDefaults().triple help_screen = helptext.HelpText( component=component, trace=trace.FireTrace(component, name='triple')) self.assertIn('NAME\n triple', help_screen) self.assertIn('SYNOPSIS\n triple ', help_screen) self.assertNotIn('DESCRIPTION', help_screen) self.assertIn( 'FLAGS\n -c, --count=COUNT\n Default: 0', help_screen) self.assertNotIn('NOTES', help_screen) def testHelpTextFunctionWithLongDefaults(self): component = tc.WithDefaults().text help_screen = helptext.HelpText( component=component, trace=trace.FireTrace(component, name='text')) self.assertIn('NAME\n text', help_screen) self.assertIn('SYNOPSIS\n text ', help_screen) self.assertNotIn('DESCRIPTION', help_screen) self.assertIn( 'FLAGS\n -s, --string=STRING\n' ' Default: \'0001020304050607080910' '1112131415161718192021222324252627282...', help_screen) self.assertNotIn('NOTES', help_screen) def testHelpTextFunctionWithKwargs(self): component = tc.fn_with_kwarg help_screen = helptext.HelpText( component=component, trace=trace.FireTrace(component, name='text')) self.assertIn('NAME\n text', help_screen) self.assertIn('SYNOPSIS\n text ARG1 ARG2 ', help_screen) self.assertIn('DESCRIPTION\n Function with kwarg', help_screen) self.assertIn( 'FLAGS\n --arg3\n Description of arg3.\n ' 'Additional undocumented flags may also be accepted.', help_screen) def testHelpTextFunctionWithKwargsAndDefaults(self): component = tc.fn_with_kwarg_and_defaults help_screen = helptext.HelpText( component=component, trace=trace.FireTrace(component, name='text')) self.assertIn('NAME\n text', help_screen) self.assertIn('SYNOPSIS\n text ARG1 ARG2 ', help_screen) self.assertIn('DESCRIPTION\n Function with kwarg', help_screen) self.assertIn( 'FLAGS\n -o, --opt=OPT\n Default: True\n' ' The following flags are also accepted.' '\n --arg3\n Description of arg3.\n ' 'Additional undocumented flags may also be accepted.', help_screen) @testutils.skipIf( sys.version_info[0:2] < (3, 5), 'Python < 3.5 does not support type hints.') def testHelpTextFunctionWithDefaultsAndTypes(self): component = ( tc.py3.WithDefaultsAndTypes().double) # pytype: disable=module-attr help_screen = helptext.HelpText( component=component, trace=trace.FireTrace(component, name='double')) self.assertIn('NAME\n double', help_screen) self.assertIn('SYNOPSIS\n double ', help_screen) self.assertIn('DESCRIPTION', help_screen) self.assertIn( 'FLAGS\n -c, --count=COUNT\n Type: float\n Default: 0', help_screen) self.assertNotIn('NOTES', help_screen) @testutils.skipIf( sys.version_info[0:2] < (3, 5), 'Python < 3.5 does not support type hints.') def testHelpTextFunctionWithTypesAndDefaultNone(self): component = ( tc.py3.WithDefaultsAndTypes().get_int) # pytype: disable=module-attr help_screen = helptext.HelpText( component=component, trace=trace.FireTrace(component, name='get_int')) self.assertIn('NAME\n get_int', help_screen) self.assertIn('SYNOPSIS\n get_int ', help_screen) self.assertNotIn('DESCRIPTION', help_screen) self.assertIn( 'FLAGS\n -v, --value=VALUE\n' ' Type: Optional[int]\n Default: None', help_screen) self.assertNotIn('NOTES', help_screen) @testutils.skipIf( sys.version_info[0:2] < (3, 5), 'Python < 3.5 does not support type hints.') def testHelpTextFunctionWithTypes(self): component = tc.py3.WithTypes().double # pytype: disable=module-attr help_screen = helptext.HelpText( component=component, trace=trace.FireTrace(component, name='double')) self.assertIn('NAME\n double', help_screen) self.assertIn('SYNOPSIS\n double COUNT', help_screen) self.assertIn('DESCRIPTION', help_screen) self.assertIn( 'POSITIONAL ARGUMENTS\n COUNT\n Type: float', help_screen) self.assertIn( 'NOTES\n You can also use flags syntax for POSITIONAL ARGUMENTS', help_screen) @testutils.skipIf( sys.version_info[0:2] < (3, 5), 'Python < 3.5 does not support type hints.') def testHelpTextFunctionWithLongTypes(self): component = tc.py3.WithTypes().long_type # pytype: disable=module-attr help_screen = helptext.HelpText( component=component, trace=trace.FireTrace(component, name='long_type')) self.assertIn('NAME\n long_type', help_screen) self.assertIn('SYNOPSIS\n long_type LONG_OBJ', help_screen) self.assertNotIn('DESCRIPTION', help_screen) # TODO(dbieber): Assert type is displayed correctly. Type displayed # differently in Travis vs in Google. # self.assertIn( # 'POSITIONAL ARGUMENTS\n LONG_OBJ\n' # ' Type: typing.Tuple[typing.Tuple[' # 'typing.Tuple[typing.Tuple[typing.Tupl...', # help_screen) self.assertIn( 'NOTES\n You can also use flags syntax for POSITIONAL ARGUMENTS', help_screen) def testHelpTextFunctionWithBuiltin(self): component = 'test'.upper help_screen = helptext.HelpText( component=component, trace=trace.FireTrace(component, 'upper')) self.assertIn('NAME\n upper', help_screen) self.assertIn('SYNOPSIS\n upper', help_screen) # We don't check description content here since the content is python # version dependent. self.assertIn('DESCRIPTION\n', help_screen) self.assertNotIn('NOTES', help_screen) def testHelpTextFunctionIntType(self): component = int help_screen = helptext.HelpText( component=component, trace=trace.FireTrace(component, 'int')) self.assertIn('NAME\n int', help_screen) self.assertIn('SYNOPSIS\n int', help_screen) # We don't check description content here since the content is python # version dependent. self.assertIn('DESCRIPTION\n', help_screen) def testHelpTextEmptyList(self): component = [] help_screen = helptext.HelpText( component=component, trace=trace.FireTrace(component, 'list')) self.assertIn('NAME\n list', help_screen) self.assertIn('SYNOPSIS\n list COMMAND', help_screen) # TODO(zuhaochen): Change assertion after custom description is # implemented for list type. self.assertNotIn('DESCRIPTION', help_screen) # We don't check the listed commands either since the list API could # potentially change between Python versions. self.assertIn('COMMANDS\n COMMAND is one of the following:\n', help_screen) def testHelpTextShortList(self): component = [10] help_screen = helptext.HelpText( component=component, trace=trace.FireTrace(component, 'list')) self.assertIn('NAME\n list', help_screen) self.assertIn('SYNOPSIS\n list COMMAND', help_screen) # TODO(zuhaochen): Change assertion after custom description is # implemented for list type. self.assertNotIn('DESCRIPTION', help_screen) # We don't check the listed commands comprehensively since the list API # could potentially change between Python versions. Check a few # functions(command) that we're confident likely remain available. self.assertIn('COMMANDS\n COMMAND is one of the following:\n', help_screen) self.assertIn(' append\n', help_screen) def testHelpTextInt(self): component = 7 help_screen = helptext.HelpText( component=component, trace=trace.FireTrace(component, '7')) self.assertIn('NAME\n 7', help_screen) self.assertIn('SYNOPSIS\n 7 COMMAND | VALUE', help_screen) # TODO(zuhaochen): Change assertion after implementing custom # description for int. self.assertNotIn('DESCRIPTION', help_screen) self.assertIn('COMMANDS\n COMMAND is one of the following:\n', help_screen) self.assertIn('VALUES\n VALUE is one of the following:\n', help_screen) def testHelpTextNoInit(self): component = tc.OldStyleEmpty help_screen = helptext.HelpText( component=component, trace=trace.FireTrace(component, 'OldStyleEmpty')) self.assertIn('NAME\n OldStyleEmpty', help_screen) self.assertIn('SYNOPSIS\n OldStyleEmpty', help_screen) @testutils.skipIf( six.PY2, 'Python 2 does not support keyword-only arguments.') def testHelpTextKeywordOnlyArgumentsWithDefault(self): component = tc.py3.KeywordOnly.with_default # pytype: disable=module-attr output = helptext.HelpText( component=component, trace=trace.FireTrace(component, 'with_default')) self.assertIn('NAME\n with_default', output) self.assertIn('FLAGS\n -x, --x=X', output) @testutils.skipIf( six.PY2, 'Python 2 does not support keyword-only arguments.') def testHelpTextKeywordOnlyArgumentsWithoutDefault(self): component = tc.py3.KeywordOnly.double # pytype: disable=module-attr output = helptext.HelpText( component=component, trace=trace.FireTrace(component, 'double')) self.assertIn('NAME\n double', output) self.assertIn('FLAGS\n -c, --count=COUNT (required)', output) @testutils.skipIf( six.PY2, 'Python 2 does not support required name-only arguments.') def testHelpTextFunctionMixedDefaults(self): component = tc.py3.HelpTextComponent().identity t = trace.FireTrace(component, name='FunctionMixedDefaults') output = helptext.HelpText(component, trace=t) self.assertIn('NAME\n FunctionMixedDefaults', output) self.assertIn('FunctionMixedDefaults ', output) self.assertIn('--alpha=ALPHA (required)', output) self.assertIn('--beta=BETA\n Default: \'0\'', output) def testHelpScreen(self): component = tc.ClassWithDocstring() t = trace.FireTrace(component, name='ClassWithDocstring') help_output = helptext.HelpText(component, t) expected_output = """ NAME ClassWithDocstring - Test class for testing help text output. SYNOPSIS ClassWithDocstring COMMAND | VALUE DESCRIPTION This is some detail description of this test class. COMMANDS COMMAND is one of the following: print_msg Prints a message. VALUES VALUE is one of the following: message The default message to print.""" self.assertEqual(textwrap.dedent(expected_output).strip(), help_output.strip()) def testHelpScreenForFunctionDocstringWithLineBreak(self): component = tc.ClassWithMultilineDocstring.example_generator t = trace.FireTrace(component, name='example_generator') help_output = helptext.HelpText(component, t) expected_output = """ NAME example_generator - Generators have a ``Yields`` section instead of a ``Returns`` section. SYNOPSIS example_generator N DESCRIPTION Generators have a ``Yields`` section instead of a ``Returns`` section. POSITIONAL ARGUMENTS N The upper limit of the range to generate, from 0 to `n` - 1. NOTES You can also use flags syntax for POSITIONAL ARGUMENTS""" self.assertEqual(textwrap.dedent(expected_output).strip(), help_output.strip()) def testHelpScreenForFunctionFunctionWithDefaultArgs(self): component = tc.WithDefaults().double t = trace.FireTrace(component, name='double') help_output = helptext.HelpText(component, t) expected_output = """ NAME double - Returns the input multiplied by 2. SYNOPSIS double DESCRIPTION Returns the input multiplied by 2. FLAGS -c, --count=COUNT Default: 0 Input number that you want to double.""" self.assertEqual(textwrap.dedent(expected_output).strip(), help_output.strip()) def testHelpTextUnderlineFlag(self): component = tc.WithDefaults().triple t = trace.FireTrace(component, name='triple') help_screen = helptext.HelpText(component, t) self.assertIn(formatting.Bold('NAME') + '\n triple', help_screen) self.assertIn( formatting.Bold('SYNOPSIS') + '\n triple ', help_screen) self.assertIn( formatting.Bold('FLAGS') + '\n -c, --' + formatting.Underline('count'), help_screen) def testHelpTextBoldCommandName(self): component = tc.ClassWithDocstring() t = trace.FireTrace(component, name='ClassWithDocstring') help_screen = helptext.HelpText(component, t) self.assertIn( formatting.Bold('NAME') + '\n ClassWithDocstring', help_screen) self.assertIn(formatting.Bold('COMMANDS') + '\n', help_screen) self.assertIn( formatting.BoldUnderline('COMMAND') + ' is one of the following:\n', help_screen) self.assertIn(formatting.Bold('print_msg') + '\n', help_screen) def testHelpTextObjectWithGroupAndValues(self): component = tc.TypedProperties() t = trace.FireTrace(component, name='TypedProperties') help_screen = helptext.HelpText( component=component, trace=t, verbose=True) print(help_screen) self.assertIn('GROUPS', help_screen) self.assertIn('GROUP is one of the following:', help_screen) self.assertIn( 'charlie\n Class with functions that have default arguments.', help_screen) self.assertIn('VALUES', help_screen) self.assertIn('VALUE is one of the following:', help_screen) self.assertIn('alpha', help_screen) def testHelpTextNameSectionCommandWithSeparator(self): component = 9 t = trace.FireTrace(component, name='int', separator='-') t.AddSeparator() help_screen = helptext.HelpText(component=component, trace=t, verbose=False) self.assertIn('int -', help_screen) self.assertNotIn('int - -', help_screen) def testHelpTextNameSectionCommandWithSeparatorVerbose(self): component = tc.WithDefaults().double t = trace.FireTrace(component, name='double', separator='-') t.AddSeparator() help_screen = helptext.HelpText(component=component, trace=t, verbose=True) self.assertIn('double -', help_screen) self.assertIn('double - -', help_screen) def testHelpTextMultipleKeywoardArgumentsWithShortArgs(self): component = tc.fn_with_multiple_defaults t = trace.FireTrace(component, name='shortargs') help_screen = helptext.HelpText(component, t) self.assertIn(formatting.Bold('NAME') + '\n shortargs', help_screen) self.assertIn( formatting.Bold('SYNOPSIS') + '\n shortargs ', help_screen) self.assertIn( formatting.Bold('FLAGS') + '\n -f, --first', help_screen) self.assertIn('\n --last', help_screen) self.assertIn('\n --late', help_screen) class UsageTest(testutils.BaseTestCase): def testUsageOutput(self): component = tc.NoDefaults() t = trace.FireTrace(component, name='NoDefaults') usage_output = helptext.UsageText(component, trace=t, verbose=False) expected_output = """ Usage: NoDefaults available commands: double | triple For detailed information on this command, run: NoDefaults --help""" self.assertEqual( usage_output, textwrap.dedent(expected_output).lstrip('\n')) def testUsageOutputVerbose(self): component = tc.NoDefaults() t = trace.FireTrace(component, name='NoDefaults') usage_output = helptext.UsageText(component, trace=t, verbose=True) expected_output = """ Usage: NoDefaults available commands: double | triple For detailed information on this command, run: NoDefaults --help""" self.assertEqual( usage_output, textwrap.dedent(expected_output).lstrip('\n')) def testUsageOutputMethod(self): component = tc.NoDefaults().double t = trace.FireTrace(component, name='NoDefaults') t.AddAccessedProperty(component, 'double', ['double'], None, None) usage_output = helptext.UsageText(component, trace=t, verbose=False) expected_output = """ Usage: NoDefaults double COUNT For detailed information on this command, run: NoDefaults double --help""" self.assertEqual( usage_output, textwrap.dedent(expected_output).lstrip('\n')) def testUsageOutputFunctionWithHelp(self): component = tc.function_with_help t = trace.FireTrace(component, name='function_with_help') usage_output = helptext.UsageText(component, trace=t, verbose=False) expected_output = """ Usage: function_with_help optional flags: --help For detailed information on this command, run: function_with_help -- --help""" self.assertEqual( usage_output, textwrap.dedent(expected_output).lstrip('\n')) def testUsageOutputFunctionWithDocstring(self): component = tc.multiplier_with_docstring t = trace.FireTrace(component, name='multiplier_with_docstring') usage_output = helptext.UsageText(component, trace=t, verbose=False) expected_output = """ Usage: multiplier_with_docstring NUM optional flags: --rate For detailed information on this command, run: multiplier_with_docstring --help""" self.assertEqual( textwrap.dedent(expected_output).lstrip('\n'), usage_output) @testutils.skipIf( six.PY2, 'Python 2 does not support required name-only arguments.') def testUsageOutputFunctionMixedDefaults(self): component = tc.py3.HelpTextComponent().identity t = trace.FireTrace(component, name='FunctionMixedDefaults') usage_output = helptext.UsageText(component, trace=t, verbose=False) expected_output = """ Usage: FunctionMixedDefaults optional flags: --beta required flags: --alpha For detailed information on this command, run: FunctionMixedDefaults --help""" expected_output = textwrap.dedent(expected_output).lstrip('\n') self.assertEqual(expected_output, usage_output) def testUsageOutputCallable(self): # This is both a group and a command. component = tc.CallableWithKeywordArgument() t = trace.FireTrace(component, name='CallableWithKeywordArgument', separator='@') usage_output = helptext.UsageText(component, trace=t, verbose=False) expected_output = """ Usage: CallableWithKeywordArgument | available commands: print_msg flags are accepted For detailed information on this command, run: CallableWithKeywordArgument -- --help""" self.assertEqual( textwrap.dedent(expected_output).lstrip('\n'), usage_output) def testUsageOutputConstructorWithParameter(self): component = tc.InstanceVars t = trace.FireTrace(component, name='InstanceVars') usage_output = helptext.UsageText(component, trace=t, verbose=False) expected_output = """ Usage: InstanceVars --arg1=ARG1 --arg2=ARG2 For detailed information on this command, run: InstanceVars --help""" self.assertEqual( textwrap.dedent(expected_output).lstrip('\n'), usage_output) def testUsageOutputConstructorWithParameterVerbose(self): component = tc.InstanceVars t = trace.FireTrace(component, name='InstanceVars') usage_output = helptext.UsageText(component, trace=t, verbose=True) expected_output = """ Usage: InstanceVars | --arg1=ARG1 --arg2=ARG2 available commands: run For detailed information on this command, run: InstanceVars --help""" self.assertEqual( textwrap.dedent(expected_output).lstrip('\n'), usage_output) def testUsageOutputEmptyDict(self): component = {} t = trace.FireTrace(component, name='EmptyDict') usage_output = helptext.UsageText(component, trace=t, verbose=True) expected_output = """ Usage: EmptyDict For detailed information on this command, run: EmptyDict --help""" self.assertEqual( textwrap.dedent(expected_output).lstrip('\n'), usage_output) def testUsageOutputNone(self): component = None t = trace.FireTrace(component, name='None') usage_output = helptext.UsageText(component, trace=t, verbose=True) expected_output = """ Usage: None For detailed information on this command, run: None --help""" self.assertEqual( textwrap.dedent(expected_output).lstrip('\n'), usage_output) def testInitRequiresFlagSyntaxSubclassNamedTuple(self): component = tc.SubPoint t = trace.FireTrace(component, name='SubPoint') usage_output = helptext.UsageText(component, trace=t, verbose=False) expected_output = 'Usage: SubPoint --x=X --y=Y' self.assertIn(expected_output, usage_output) if __name__ == '__main__': testutils.main()