| Viewing file:  test_shellcomp.py (21.28 KB)      -rw-r--r-- Select action/file-type:
 
  (+) |  (+) |  (+) | Code (+) | Session (+) |  (+) | SDB (+) |  (+) |  (+) |  (+) |  (+) |  (+) | 
 
# Copyright (c) Twisted Matrix Laboratories.# See LICENSE for details.
 
 """
 Test cases for twisted.python._shellcomp
 """
 
 import sys
 from cStringIO import StringIO
 
 from twisted.trial import unittest
 from twisted.python import _shellcomp, usage, reflect
 from twisted.python.usage import Completions, Completer, CompleteFiles
 from twisted.python.usage import CompleteList
 
 
 
 class ZshScriptTestMeta(type):
 """
 Metaclass of ZshScriptTestMixin.
 """
 def __new__(cls, name, bases, attrs):
 def makeTest(cmdName, optionsFQPN):
 def runTest(self):
 return test_genZshFunction(self, cmdName, optionsFQPN)
 return runTest
 
 # add test_ methods to the class for each script
 # we are testing.
 if 'generateFor' in attrs:
 for cmdName, optionsFQPN in attrs['generateFor']:
 test = makeTest(cmdName, optionsFQPN)
 attrs['test_genZshFunction_' + cmdName] = test
 
 return type.__new__(cls, name, bases, attrs)
 
 
 
 class ZshScriptTestMixin(object):
 """
 Integration test helper to show that C{usage.Options} classes can have zsh
 completion functions generated for them without raising errors.
 
 In your subclasses set a class variable like so:
 
 #            | cmd name | Fully Qualified Python Name of Options class |
 #
 generateFor = [('conch',  'twisted.conch.scripts.conch.ClientOptions'),
 ('twistd', 'twisted.scripts.twistd.ServerOptions'),
 ]
 
 Each package that contains Twisted scripts should contain one TestCase
 subclass which also inherits from this mixin, and contains a C{generateFor}
 list appropriate for the scripts in that package.
 """
 __metaclass__ = ZshScriptTestMeta
 
 
 
 def test_genZshFunction(self, cmdName, optionsFQPN):
 """
 Generate completion functions for given twisted command - no errors
 should be raised
 
 @type cmdName: C{str}
 @param cmdName: The name of the command-line utility e.g. 'twistd'
 
 @type optionsFQPN: C{str}
 @param optionsFQPN: The Fully Qualified Python Name of the C{Options}
 class to be tested.
 """
 outputFile = StringIO()
 self.patch(usage.Options, '_shellCompFile', outputFile)
 
 # some scripts won't import or instantiate because of missing
 # dependencies (pyOpenSSL, etc) so we have to skip them.
 try:
 o = reflect.namedAny(optionsFQPN)()
 except Exception, e:
 raise unittest.SkipTest("Couldn't import or instantiate "
 "Options class: %s" % (e,))
 
 try:
 o.parseOptions(["", "--_shell-completion", "zsh:2"])
 except ImportError, e:
 # this can happen for commands which don't have all
 # the necessary dependencies installed. skip test.
 # skip
 raise unittest.SkipTest("ImportError calling parseOptions(): %s", (e,))
 except SystemExit:
 pass # expected
 else:
 self.fail('SystemExit not raised')
 outputFile.seek(0)
 # test that we got some output
 self.assertEqual(1, len(outputFile.read(1)))
 outputFile.seek(0)
 outputFile.truncate()
 
 # now, if it has sub commands, we have to test those too
 if hasattr(o, 'subCommands'):
 for (cmd, short, parser, doc) in o.subCommands:
 try:
 o.parseOptions([cmd, "", "--_shell-completion",
 "zsh:3"])
 except ImportError, e:
 # this can happen for commands which don't have all
 # the necessary dependencies installed. skip test.
 raise unittest.SkipTest("ImportError calling parseOptions() "
 "on subcommand: %s", (e,))
 except SystemExit:
 pass # expected
 else:
 self.fail('SystemExit not raised')
 
 outputFile.seek(0)
 # test that we got some output
 self.assertEqual(1, len(outputFile.read(1)))
 outputFile.seek(0)
 outputFile.truncate()
 
 # flushed because we don't want DeprecationWarnings to be printed when
 # running these test cases.
 self.flushWarnings()
 
 
 
 class ZshTests(unittest.TestCase):
 """
 Tests for zsh completion code
 """
 def test_accumulateMetadata(self):
 """
 Are `compData' attributes you can place on Options classes
 picked up correctly?
 """
 opts = FighterAceExtendedOptions()
 ag = _shellcomp.ZshArgumentsGenerator(opts, 'ace', 'dummy_value')
 
 descriptions = FighterAceOptions.compData.descriptions.copy()
 descriptions.update(FighterAceExtendedOptions.compData.descriptions)
 
 self.assertEqual(ag.descriptions, descriptions)
 self.assertEqual(ag.multiUse,
 set(FighterAceOptions.compData.multiUse))
 self.assertEqual(ag.mutuallyExclusive,
 FighterAceOptions.compData.mutuallyExclusive)
 
 optActions = FighterAceOptions.compData.optActions.copy()
 optActions.update(FighterAceExtendedOptions.compData.optActions)
 self.assertEqual(ag.optActions, optActions)
 
 self.assertEqual(ag.extraActions,
 FighterAceOptions.compData.extraActions)
 
 
 def test_mutuallyExclusiveCornerCase(self):
 """
 Exercise a corner-case of ZshArgumentsGenerator.makeExcludesDict()
 where the long option name already exists in the `excludes` dict being
 built.
 """
 class OddFighterAceOptions(FighterAceExtendedOptions):
 # since "fokker", etc, are already defined as mutually-
 # exclusive on the super-class, defining them again here forces
 # the corner-case to be exercised.
 optFlags = [['anatra', None,
 'Select the Anatra DS as your dogfighter aircraft']]
 compData = Completions(
 mutuallyExclusive=[['anatra', 'fokker', 'albatros',
 'spad', 'bristol']])
 
 opts = OddFighterAceOptions()
 ag = _shellcomp.ZshArgumentsGenerator(opts, 'ace', 'dummy_value')
 
 expected = {
 'albatros': set(['anatra', 'b', 'bristol', 'f',
 'fokker', 's', 'spad']),
 'anatra': set(['a', 'albatros', 'b', 'bristol',
 'f', 'fokker', 's', 'spad']),
 'bristol': set(['a', 'albatros', 'anatra', 'f',
 'fokker', 's', 'spad']),
 'fokker': set(['a', 'albatros', 'anatra', 'b',
 'bristol', 's', 'spad']),
 'spad': set(['a', 'albatros', 'anatra', 'b',
 'bristol', 'f', 'fokker'])}
 
 self.assertEqual(ag.excludes, expected)
 
 
 def test_accumulateAdditionalOptions(self):
 """
 We pick up options that are only defined by having an
 appropriately named method on your Options class,
 e.g. def opt_foo(self, foo)
 """
 opts = FighterAceExtendedOptions()
 ag = _shellcomp.ZshArgumentsGenerator(opts, 'ace', 'dummy_value')
 
 self.assertIn('nocrash', ag.flagNameToDefinition)
 self.assertIn('nocrash', ag.allOptionsNameToDefinition)
 
 self.assertIn('difficulty', ag.paramNameToDefinition)
 self.assertIn('difficulty', ag.allOptionsNameToDefinition)
 
 
 def test_verifyZshNames(self):
 """
 Using a parameter/flag name that doesn't exist
 will raise an error
 """
 class TmpOptions(FighterAceExtendedOptions):
 # Note typo of detail
 compData = Completions(optActions={'detaill' : None})
 
 self.assertRaises(ValueError, _shellcomp.ZshArgumentsGenerator,
 TmpOptions(), 'ace', 'dummy_value')
 
 class TmpOptions2(FighterAceExtendedOptions):
 # Note that 'foo' and 'bar' are not real option
 # names defined in this class
 compData = Completions(
 mutuallyExclusive=[("foo", "bar")])
 
 self.assertRaises(ValueError, _shellcomp.ZshArgumentsGenerator,
 TmpOptions2(), 'ace', 'dummy_value')
 
 
 def test_zshCode(self):
 """
 Generate a completion function, and test the textual output
 against a known correct output
 """
 outputFile = StringIO()
 self.patch(usage.Options, '_shellCompFile', outputFile)
 self.patch(sys, 'argv', ["silly", "", "--_shell-completion", "zsh:2"])
 opts = SimpleProgOptions()
 self.assertRaises(SystemExit, opts.parseOptions)
 self.assertEqual(testOutput1, outputFile.getvalue())
 
 
 def test_zshCodeWithSubs(self):
 """
 Generate a completion function with subcommands,
 and test the textual output against a known correct output
 """
 outputFile = StringIO()
 self.patch(usage.Options, '_shellCompFile', outputFile)
 self.patch(sys, 'argv', ["silly2", "", "--_shell-completion", "zsh:2"])
 opts = SimpleProgWithSubcommands()
 self.assertRaises(SystemExit, opts.parseOptions)
 self.assertEqual(testOutput2, outputFile.getvalue())
 
 
 def test_incompleteCommandLine(self):
 """
 Completion still happens even if a command-line is given
 that would normally throw UsageError.
 """
 outputFile = StringIO()
 self.patch(usage.Options, '_shellCompFile', outputFile)
 opts = FighterAceOptions()
 
 self.assertRaises(SystemExit, opts.parseOptions,
 ["--fokker", "server", "--unknown-option",
 "--unknown-option2",
 "--_shell-completion", "zsh:5"])
 outputFile.seek(0)
 # test that we got some output
 self.assertEqual(1, len(outputFile.read(1)))
 
 
 def test_incompleteCommandLine_case2(self):
 """
 Completion still happens even if a command-line is given
 that would normally throw UsageError.
 
 The existence of --unknown-option prior to the subcommand
 will break subcommand detection... but we complete anyway
 """
 outputFile = StringIO()
 self.patch(usage.Options, '_shellCompFile', outputFile)
 opts = FighterAceOptions()
 
 self.assertRaises(SystemExit, opts.parseOptions,
 ["--fokker", "--unknown-option", "server",
 "--list-server", "--_shell-completion", "zsh:5"])
 outputFile.seek(0)
 # test that we got some output
 self.assertEqual(1, len(outputFile.read(1)))
 
 outputFile.seek(0)
 outputFile.truncate()
 
 
 def test_incompleteCommandLine_case3(self):
 """
 Completion still happens even if a command-line is given
 that would normally throw UsageError.
 
 Break subcommand detection in a different way by providing
 an invalid subcommand name.
 """
 outputFile = StringIO()
 self.patch(usage.Options, '_shellCompFile', outputFile)
 opts = FighterAceOptions()
 
 self.assertRaises(SystemExit, opts.parseOptions,
 ["--fokker", "unknown-subcommand",
 "--list-server", "--_shell-completion", "zsh:4"])
 outputFile.seek(0)
 # test that we got some output
 self.assertEqual(1, len(outputFile.read(1)))
 
 
 def test_skipSubcommandList(self):
 """
 Ensure the optimization which skips building the subcommand list
 under certain conditions isn't broken.
 """
 outputFile = StringIO()
 self.patch(usage.Options, '_shellCompFile', outputFile)
 opts = FighterAceOptions()
 
 self.assertRaises(SystemExit, opts.parseOptions,
 ["--alba", "--_shell-completion", "zsh:2"])
 outputFile.seek(0)
 # test that we got some output
 self.assertEqual(1, len(outputFile.read(1)))
 
 
 def test_poorlyDescribedOptMethod(self):
 """
 Test corner case fetching an option description from a method docstring
 """
 opts = FighterAceOptions()
 argGen = _shellcomp.ZshArgumentsGenerator(opts, 'ace', None)
 
 descr = argGen.getDescription('silly')
 
 # docstring for opt_silly is useless so it should just use the
 # option name as the description
 self.assertEqual(descr, 'silly')
 
 
 def test_brokenActions(self):
 """
 A C{Completer} with repeat=True may only be used as the
 last item in the extraActions list.
 """
 class BrokenActions(usage.Options):
 compData = usage.Completions(
 extraActions=[usage.Completer(repeat=True),
 usage.Completer()]
 )
 
 outputFile = StringIO()
 opts = BrokenActions()
 self.patch(opts, '_shellCompFile', outputFile)
 self.assertRaises(ValueError, opts.parseOptions,
 ["", "--_shell-completion", "zsh:2"])
 
 
 def test_optMethodsDontOverride(self):
 """
 opt_* methods on Options classes should not override the
 data provided in optFlags or optParameters.
 """
 class Options(usage.Options):
 optFlags = [['flag', 'f', 'A flag']]
 optParameters = [['param', 'p', None, 'A param']]
 
 def opt_flag(self):
 """ junk description """
 
 def opt_param(self, param):
 """ junk description """
 
 opts = Options()
 argGen = _shellcomp.ZshArgumentsGenerator(opts, 'ace', None)
 
 self.assertEqual(argGen.getDescription('flag'), 'A flag')
 self.assertEqual(argGen.getDescription('param'), 'A param')
 
 
 
 class EscapeTests(unittest.TestCase):
 def test_escape(self):
 """
 Verify _shellcomp.escape() function
 """
 esc = _shellcomp.escape
 
 test = "$"
 self.assertEqual(esc(test), "'$'")
 
 test = 'A--\'$"\\`--B'
 self.assertEqual(esc(test), '"A--\'\\$\\"\\\\\\`--B"')
 
 
 
 class CompleterNotImplementedTests(unittest.TestCase):
 """
 Test that using an unknown shell constant with SubcommandAction
 raises NotImplementedError
 
 The other Completer() subclasses are tested in test_usage.py
 """
 def test_unknownShell(self):
 """
 Using an unknown shellType should raise NotImplementedError
 """
 action = _shellcomp.SubcommandAction()
 
 self.assertRaises(NotImplementedError, action._shellCode,
 None, "bad_shell_type")
 
 
 
 class FighterAceServerOptions(usage.Options):
 """
 Options for FighterAce 'server' subcommand
 """
 optFlags = [['list-server', None,
 'List this server with the online FighterAce network']]
 optParameters = [['packets-per-second', None,
 'Number of update packets to send per second', '20']]
 
 
 
 class FighterAceOptions(usage.Options):
 """
 Command-line options for an imaginary `Fighter Ace` game
 """
 optFlags = [['fokker', 'f',
 'Select the Fokker Dr.I as your dogfighter aircraft'],
 ['albatros', 'a',
 'Select the Albatros D-III as your dogfighter aircraft'],
 ['spad', 's',
 'Select the SPAD S.VII as your dogfighter aircraft'],
 ['bristol', 'b',
 'Select the Bristol Scout as your dogfighter aircraft'],
 ['physics', 'p',
 'Enable secret Twisted physics engine'],
 ['jam', 'j',
 'Enable a small chance that your machine guns will jam!'],
 ['verbose', 'v',
 'Verbose logging (may be specified more than once)'],
 ]
 
 optParameters = [['pilot-name', None, "What's your name, Ace?",
 'Manfred von Richthofen'],
 ['detail', 'd',
 'Select the level of rendering detail (1-5)', '3'],
 ]
 
 subCommands = [['server', None, FighterAceServerOptions,
 'Start FighterAce game-server.'],
 ]
 
 compData = Completions(
 descriptions={'physics' : 'Twisted-Physics',
 'detail' : 'Rendering detail level'},
 multiUse=['verbose'],
 mutuallyExclusive=[['fokker', 'albatros', 'spad',
 'bristol']],
 optActions={'detail' : CompleteList(['1' '2' '3'
 '4' '5'])},
 extraActions=[CompleteFiles(descr='saved game file to load')]
 )
 
 def opt_silly(self):
 # A silly option which nobody can explain
 """ """
 
 
 
 class FighterAceExtendedOptions(FighterAceOptions):
 """
 Extend the options and zsh metadata provided by FighterAceOptions.
 _shellcomp must accumulate options and metadata from all classes in the
 hiearchy so this is important to test.
 """
 optFlags = [['no-stalls', None,
 'Turn off the ability to stall your aircraft']]
 optParameters = [['reality-level', None,
 'Select the level of physics reality (1-5)', '5']]
 
 compData = Completions(
 descriptions={'no-stalls' : 'Can\'t stall your plane'},
 optActions={'reality-level' :
 Completer(descr='Physics reality level')}
 )
 
 def opt_nocrash(self):
 """
 Select that you can't crash your plane
 """
 
 
 def opt_difficulty(self, difficulty):
 """
 How tough are you? (1-10)
 """
 
 
 
 def _accuracyAction():
 # add tick marks just to exercise quoting
 return CompleteList(['1', '2', '3'], descr='Accuracy\'`?')
 
 
 
 class SimpleProgOptions(usage.Options):
 """
 Command-line options for a `Silly` imaginary program
 """
 optFlags = [['color', 'c', 'Turn on color output'],
 ['gray', 'g', 'Turn on gray-scale output'],
 ['verbose', 'v',
 'Verbose logging (may be specified more than once)'],
 ]
 
 optParameters = [['optimization', None, '5',
 'Select the level of optimization (1-5)'],
 ['accuracy', 'a', '3',
 'Select the level of accuracy (1-3)'],
 ]
 
 
 compData = Completions(
 descriptions={'color' : 'Color on',
 'optimization' : 'Optimization level'},
 multiUse=['verbose'],
 mutuallyExclusive=[['color', 'gray']],
 optActions={'optimization' : CompleteList(['1', '2', '3', '4', '5'],
 descr='Optimization?'),
 'accuracy' : _accuracyAction},
 extraActions=[CompleteFiles(descr='output file')]
 )
 
 def opt_X(self):
 """
 usage.Options does not recognize single-letter opt_ methods
 """
 
 
 
 class SimpleProgSub1(usage.Options):
 optFlags = [['sub-opt', 's', 'Sub Opt One']]
 
 
 
 class SimpleProgSub2(usage.Options):
 optFlags = [['sub-opt', 's', 'Sub Opt Two']]
 
 
 
 class SimpleProgWithSubcommands(SimpleProgOptions):
 optFlags = [['some-option'],
 ['other-option', 'o']]
 
 optParameters = [['some-param'],
 ['other-param', 'p'],
 ['another-param', 'P', 'Yet Another Param']]
 
 subCommands = [ ['sub1', None, SimpleProgSub1, 'Sub Command 1'],
 ['sub2', None, SimpleProgSub2, 'Sub Command 2']]
 
 
 
 testOutput1 = """#compdef silly
 
 _arguments -s -A "-*" \\
 ':output file (*):_files -g "*"' \\
 "(--accuracy)-a[Select the level of accuracy (1-3)]:Accuracy'\`?:(1 2 3)" \\
 "(-a)--accuracy=[Select the level of accuracy (1-3)]:Accuracy'\`?:(1 2 3)" \\
 '(--color --gray -g)-c[Color on]' \\
 '(--gray -c -g)--color[Color on]' \\
 '(--color --gray -c)-g[Turn on gray-scale output]' \\
 '(--color -c -g)--gray[Turn on gray-scale output]' \\
 '--help[Display this help and exit.]' \\
 '--optimization=[Optimization level]:Optimization?:(1 2 3 4 5)' \\
 '*-v[Verbose logging (may be specified more than once)]' \\
 '*--verbose[Verbose logging (may be specified more than once)]' \\
 '--version[Display Twisted version and exit.]' \\
 && return 0
 """
 
 # with sub-commands
 testOutput2 = """#compdef silly2
 
 _arguments -s -A "-*" \\
 '*::subcmd:->subcmd' \\
 ':output file (*):_files -g "*"' \\
 "(--accuracy)-a[Select the level of accuracy (1-3)]:Accuracy'\`?:(1 2 3)" \\
 "(-a)--accuracy=[Select the level of accuracy (1-3)]:Accuracy'\`?:(1 2 3)" \\
 '(--another-param)-P[another-param]:another-param:_files' \\
 '(-P)--another-param=[another-param]:another-param:_files' \\
 '(--color --gray -g)-c[Color on]' \\
 '(--gray -c -g)--color[Color on]' \\
 '(--color --gray -c)-g[Turn on gray-scale output]' \\
 '(--color -c -g)--gray[Turn on gray-scale output]' \\
 '--help[Display this help and exit.]' \\
 '--optimization=[Optimization level]:Optimization?:(1 2 3 4 5)' \\
 '(--other-option)-o[other-option]' \\
 '(-o)--other-option[other-option]' \\
 '(--other-param)-p[other-param]:other-param:_files' \\
 '(-p)--other-param=[other-param]:other-param:_files' \\
 '--some-option[some-option]' \\
 '--some-param=[some-param]:some-param:_files' \\
 '*-v[Verbose logging (may be specified more than once)]' \\
 '*--verbose[Verbose logging (may be specified more than once)]' \\
 '--version[Display Twisted version and exit.]' \\
 && return 0
 local _zsh_subcmds_array
 _zsh_subcmds_array=(
 "sub1:Sub Command 1"
 "sub2:Sub Command 2"
 )
 
 _describe "sub-command" _zsh_subcmds_array
 """
 
 |