diff options
Diffstat (limited to 'third_party/py/gflags/tests/gflags_helpxml_test.py')
-rwxr-xr-x | third_party/py/gflags/tests/gflags_helpxml_test.py | 535 |
1 files changed, 535 insertions, 0 deletions
diff --git a/third_party/py/gflags/tests/gflags_helpxml_test.py b/third_party/py/gflags/tests/gflags_helpxml_test.py new file mode 100755 index 0000000000..fd78004b73 --- /dev/null +++ b/third_party/py/gflags/tests/gflags_helpxml_test.py @@ -0,0 +1,535 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Unit tests for the XML-format help generated by the gflags.py module.""" + +__author__ = 'salcianu@google.com (Alex Salcianu)' + + +import string +import StringIO +import sys +import xml.dom.minidom +import xml.sax.saxutils +import gflags_googletest as googletest +import gflags +from flags_modules_for_testing import module_bar + + +class _MakeXMLSafeTest(googletest.TestCase): + + def _Check(self, s, expected_output): + self.assertEqual(gflags._MakeXMLSafe(s), expected_output) + + def testMakeXMLSafe(self): + self._Check('plain text', 'plain text') + self._Check('(x < y) && (a >= b)', + '(x < y) && (a >= b)') + # Some characters with ASCII code < 32 are illegal in XML 1.0 and + # are removed by us. However, '\n', '\t', and '\r' are legal. + self._Check('\x09\x0btext \x02 with\x0dsome \x08 good & bad chars', + '\ttext with\rsome good & bad chars') + + +def _ListSeparatorsInXMLFormat(separators, indent=''): + """Generates XML encoding of a list of list separators. + + Args: + separators: A list of list separators. Usually, this should be a + string whose characters are the valid list separators, e.g., ',' + means that both comma (',') and space (' ') are valid list + separators. + indent: A string that is added at the beginning of each generated + XML element. + + Returns: + A string. + """ + result = '' + separators = list(separators) + separators.sort() + for sep_char in separators: + result += ('%s<list_separator>%s</list_separator>\n' % + (indent, repr(sep_char))) + return result + + +class WriteFlagHelpInXMLFormatTest(googletest.TestCase): + """Test the XML-format help for a single flag at a time. + + There is one test* method for each kind of DEFINE_* declaration. + """ + + def setUp(self): + # self.fv is a FlagValues object, just like gflags.FLAGS. Each + # test registers one flag with this FlagValues. + self.fv = gflags.FlagValues() + + def _CheckFlagHelpInXML(self, flag_name, module_name, + expected_output, is_key=False): + # StringIO.StringIO is a file object that writes into a memory string. + sio = StringIO.StringIO() + flag_obj = self.fv[flag_name] + flag_obj.WriteInfoInXMLFormat(sio, module_name, is_key=is_key, indent=' ') + self.assertMultiLineEqual(sio.getvalue(), expected_output) + sio.close() + + def testFlagHelpInXML_Int(self): + gflags.DEFINE_integer('index', 17, 'An integer flag', flag_values=self.fv) + expected_output_pattern = ( + ' <flag>\n' + ' <file>module.name</file>\n' + ' <name>index</name>\n' + ' <meaning>An integer flag</meaning>\n' + ' <default>17</default>\n' + ' <current>%d</current>\n' + ' <type>int</type>\n' + ' </flag>\n') + self._CheckFlagHelpInXML('index', 'module.name', + expected_output_pattern % 17) + # Check that the output is correct even when the current value of + # a flag is different from the default one. + self.fv['index'].value = 20 + self._CheckFlagHelpInXML('index', 'module.name', + expected_output_pattern % 20) + + def testFlagHelpInXML_IntWithBounds(self): + gflags.DEFINE_integer('nb_iters', 17, 'An integer flag', + lower_bound=5, upper_bound=27, + flag_values=self.fv) + expected_output = ( + ' <flag>\n' + ' <key>yes</key>\n' + ' <file>module.name</file>\n' + ' <name>nb_iters</name>\n' + ' <meaning>An integer flag</meaning>\n' + ' <default>17</default>\n' + ' <current>17</current>\n' + ' <type>int</type>\n' + ' <lower_bound>5</lower_bound>\n' + ' <upper_bound>27</upper_bound>\n' + ' </flag>\n') + self._CheckFlagHelpInXML('nb_iters', 'module.name', + expected_output, is_key=True) + + def testFlagHelpInXML_String(self): + gflags.DEFINE_string('file_path', '/path/to/my/dir', 'A test string flag.', + flag_values=self.fv) + expected_output = ( + ' <flag>\n' + ' <file>simple_module</file>\n' + ' <name>file_path</name>\n' + ' <meaning>A test string flag.</meaning>\n' + ' <default>/path/to/my/dir</default>\n' + ' <current>/path/to/my/dir</current>\n' + ' <type>string</type>\n' + ' </flag>\n') + self._CheckFlagHelpInXML('file_path', 'simple_module', + expected_output) + + def testFlagHelpInXML_StringWithXMLIllegalChars(self): + gflags.DEFINE_string('file_path', '/path/to/\x08my/dir', + 'A test string flag.', flag_values=self.fv) + # '\x08' is not a legal character in XML 1.0 documents. Our + # current code purges such characters from the generated XML. + expected_output = ( + ' <flag>\n' + ' <file>simple_module</file>\n' + ' <name>file_path</name>\n' + ' <meaning>A test string flag.</meaning>\n' + ' <default>/path/to/my/dir</default>\n' + ' <current>/path/to/my/dir</current>\n' + ' <type>string</type>\n' + ' </flag>\n') + self._CheckFlagHelpInXML('file_path', 'simple_module', + expected_output) + + def testFlagHelpInXML_Boolean(self): + gflags.DEFINE_boolean('use_hack', False, 'Use performance hack', + flag_values=self.fv) + expected_output = ( + ' <flag>\n' + ' <key>yes</key>\n' + ' <file>a_module</file>\n' + ' <name>use_hack</name>\n' + ' <meaning>Use performance hack</meaning>\n' + ' <default>false</default>\n' + ' <current>false</current>\n' + ' <type>bool</type>\n' + ' </flag>\n') + self._CheckFlagHelpInXML('use_hack', 'a_module', + expected_output, is_key=True) + + def testFlagHelpInXML_Enum(self): + gflags.DEFINE_enum('cc_version', 'stable', ['stable', 'experimental'], + 'Compiler version to use.', flag_values=self.fv) + expected_output = ( + ' <flag>\n' + ' <file>tool</file>\n' + ' <name>cc_version</name>\n' + ' <meaning><stable|experimental>: ' + 'Compiler version to use.</meaning>\n' + ' <default>stable</default>\n' + ' <current>stable</current>\n' + ' <type>string enum</type>\n' + ' <enum_value>stable</enum_value>\n' + ' <enum_value>experimental</enum_value>\n' + ' </flag>\n') + self._CheckFlagHelpInXML('cc_version', 'tool', expected_output) + + def testFlagHelpInXML_CommaSeparatedList(self): + gflags.DEFINE_list('files', 'a.cc,a.h,archive/old.zip', + 'Files to process.', flag_values=self.fv) + expected_output = ( + ' <flag>\n' + ' <file>tool</file>\n' + ' <name>files</name>\n' + ' <meaning>Files to process.</meaning>\n' + ' <default>a.cc,a.h,archive/old.zip</default>\n' + ' <current>[\'a.cc\', \'a.h\', \'archive/old.zip\']</current>\n' + ' <type>comma separated list of strings</type>\n' + ' <list_separator>\',\'</list_separator>\n' + ' </flag>\n') + self._CheckFlagHelpInXML('files', 'tool', expected_output) + + def testListAsDefaultArgument_CommaSeparatedList(self): + gflags.DEFINE_list('allow_users', ['alice', 'bob'], + 'Users with access.', flag_values=self.fv) + expected_output = ( + ' <flag>\n' + ' <file>tool</file>\n' + ' <name>allow_users</name>\n' + ' <meaning>Users with access.</meaning>\n' + ' <default>alice,bob</default>\n' + ' <current>[\'alice\', \'bob\']</current>\n' + ' <type>comma separated list of strings</type>\n' + ' <list_separator>\',\'</list_separator>\n' + ' </flag>\n') + self._CheckFlagHelpInXML('allow_users', 'tool', expected_output) + + def testFlagHelpInXML_SpaceSeparatedList(self): + gflags.DEFINE_spaceseplist('dirs', 'src libs bin', + 'Directories to search.', flag_values=self.fv) + expected_output = ( + ' <flag>\n' + ' <file>tool</file>\n' + ' <name>dirs</name>\n' + ' <meaning>Directories to search.</meaning>\n' + ' <default>src libs bin</default>\n' + ' <current>[\'src\', \'libs\', \'bin\']</current>\n' + ' <type>whitespace separated list of strings</type>\n' + 'LIST_SEPARATORS' + ' </flag>\n').replace('LIST_SEPARATORS', + _ListSeparatorsInXMLFormat(string.whitespace, + indent=' ')) + self._CheckFlagHelpInXML('dirs', 'tool', expected_output) + + def testFlagHelpInXML_MultiString(self): + gflags.DEFINE_multistring('to_delete', ['a.cc', 'b.h'], + 'Files to delete', flag_values=self.fv) + expected_output = ( + ' <flag>\n' + ' <file>tool</file>\n' + ' <name>to_delete</name>\n' + ' <meaning>Files to delete;\n ' + 'repeat this option to specify a list of values</meaning>\n' + ' <default>[\'a.cc\', \'b.h\']</default>\n' + ' <current>[\'a.cc\', \'b.h\']</current>\n' + ' <type>multi string</type>\n' + ' </flag>\n') + self._CheckFlagHelpInXML('to_delete', 'tool', expected_output) + + def testFlagHelpInXML_MultiInt(self): + gflags.DEFINE_multi_int('cols', [5, 7, 23], + 'Columns to select', flag_values=self.fv) + expected_output = ( + ' <flag>\n' + ' <file>tool</file>\n' + ' <name>cols</name>\n' + ' <meaning>Columns to select;\n ' + 'repeat this option to specify a list of values</meaning>\n' + ' <default>[5, 7, 23]</default>\n' + ' <current>[5, 7, 23]</current>\n' + ' <type>multi int</type>\n' + ' </flag>\n') + self._CheckFlagHelpInXML('cols', 'tool', expected_output) + + +# The next EXPECTED_HELP_XML_* constants are parts of a template for +# the expected XML output from WriteHelpInXMLFormatTest below. When +# we assemble these parts into a single big string, we'll take into +# account the ordering between the name of the main module and the +# name of module_bar. Next, we'll fill in the docstring for this +# module (%(usage_doc)s), the name of the main module +# (%(main_module_name)s) and the name of the module module_bar +# (%(module_bar_name)s). See WriteHelpInXMLFormatTest below. +# +# NOTE: given the current implementation of _GetMainModule(), we +# already know the ordering between the main module and module_bar. +# However, there is no guarantee that _GetMainModule will never be +# changed in the future (especially since it's far from perfect). +EXPECTED_HELP_XML_START = """\ +<?xml version="1.0"?> +<AllFlags> + <program>gflags_helpxml_test.py</program> + <usage>%(usage_doc)s</usage> +""" + +EXPECTED_HELP_XML_FOR_FLAGS_FROM_MAIN_MODULE = """\ + <flag> + <key>yes</key> + <file>%(main_module_name)s</file> + <name>allow_users</name> + <meaning>Users with access.</meaning> + <default>alice,bob</default> + <current>['alice', 'bob']</current> + <type>comma separated list of strings</type> + <list_separator>','</list_separator> + </flag> + <flag> + <key>yes</key> + <file>%(main_module_name)s</file> + <name>cc_version</name> + <meaning><stable|experimental>: Compiler version to use.</meaning> + <default>stable</default> + <current>stable</current> + <type>string enum</type> + <enum_value>stable</enum_value> + <enum_value>experimental</enum_value> + </flag> + <flag> + <key>yes</key> + <file>%(main_module_name)s</file> + <name>cols</name> + <meaning>Columns to select; + repeat this option to specify a list of values</meaning> + <default>[5, 7, 23]</default> + <current>[5, 7, 23]</current> + <type>multi int</type> + </flag> + <flag> + <key>yes</key> + <file>%(main_module_name)s</file> + <name>dirs</name> + <meaning>Directories to create.</meaning> + <default>src libs bins</default> + <current>['src', 'libs', 'bins']</current> + <type>whitespace separated list of strings</type> +%(whitespace_separators)s </flag> + <flag> + <key>yes</key> + <file>%(main_module_name)s</file> + <name>file_path</name> + <meaning>A test string flag.</meaning> + <default>/path/to/my/dir</default> + <current>/path/to/my/dir</current> + <type>string</type> + </flag> + <flag> + <key>yes</key> + <file>%(main_module_name)s</file> + <name>files</name> + <meaning>Files to process.</meaning> + <default>a.cc,a.h,archive/old.zip</default> + <current>['a.cc', 'a.h', 'archive/old.zip']</current> + <type>comma separated list of strings</type> + <list_separator>\',\'</list_separator> + </flag> + <flag> + <key>yes</key> + <file>%(main_module_name)s</file> + <name>index</name> + <meaning>An integer flag</meaning> + <default>17</default> + <current>17</current> + <type>int</type> + </flag> + <flag> + <key>yes</key> + <file>%(main_module_name)s</file> + <name>nb_iters</name> + <meaning>An integer flag</meaning> + <default>17</default> + <current>17</current> + <type>int</type> + <lower_bound>5</lower_bound> + <upper_bound>27</upper_bound> + </flag> + <flag> + <key>yes</key> + <file>%(main_module_name)s</file> + <name>to_delete</name> + <meaning>Files to delete; + repeat this option to specify a list of values</meaning> + <default>['a.cc', 'b.h']</default> + <current>['a.cc', 'b.h']</current> + <type>multi string</type> + </flag> + <flag> + <key>yes</key> + <file>%(main_module_name)s</file> + <name>use_hack</name> + <meaning>Use performance hack</meaning> + <default>false</default> + <current>false</current> + <type>bool</type> + </flag> +""" + +EXPECTED_HELP_XML_FOR_FLAGS_FROM_MODULE_BAR = """\ + <flag> + <file>%(module_bar_name)s</file> + <name>tmod_bar_t</name> + <meaning>Sample int flag.</meaning> + <default>4</default> + <current>4</current> + <type>int</type> + </flag> + <flag> + <key>yes</key> + <file>%(module_bar_name)s</file> + <name>tmod_bar_u</name> + <meaning>Sample int flag.</meaning> + <default>5</default> + <current>5</current> + <type>int</type> + </flag> + <flag> + <file>%(module_bar_name)s</file> + <name>tmod_bar_v</name> + <meaning>Sample int flag.</meaning> + <default>6</default> + <current>6</current> + <type>int</type> + </flag> + <flag> + <file>%(module_bar_name)s</file> + <name>tmod_bar_x</name> + <meaning>Boolean flag.</meaning> + <default>true</default> + <current>true</current> + <type>bool</type> + </flag> + <flag> + <file>%(module_bar_name)s</file> + <name>tmod_bar_y</name> + <meaning>String flag.</meaning> + <default>default</default> + <current>default</current> + <type>string</type> + </flag> + <flag> + <key>yes</key> + <file>%(module_bar_name)s</file> + <name>tmod_bar_z</name> + <meaning>Another boolean flag from module bar.</meaning> + <default>false</default> + <current>false</current> + <type>bool</type> + </flag> +""" + +EXPECTED_HELP_XML_END = """\ +</AllFlags> +""" + + +class WriteHelpInXMLFormatTest(googletest.TestCase): + """Big test of FlagValues.WriteHelpInXMLFormat, with several flags.""" + + def testWriteHelpInXMLFormat(self): + fv = gflags.FlagValues() + # Since these flags are defined by the top module, they are all key. + gflags.DEFINE_integer('index', 17, 'An integer flag', flag_values=fv) + gflags.DEFINE_integer('nb_iters', 17, 'An integer flag', + lower_bound=5, upper_bound=27, flag_values=fv) + gflags.DEFINE_string('file_path', '/path/to/my/dir', 'A test string flag.', + flag_values=fv) + gflags.DEFINE_boolean('use_hack', False, 'Use performance hack', + flag_values=fv) + gflags.DEFINE_enum('cc_version', 'stable', ['stable', 'experimental'], + 'Compiler version to use.', flag_values=fv) + gflags.DEFINE_list('files', 'a.cc,a.h,archive/old.zip', + 'Files to process.', flag_values=fv) + gflags.DEFINE_list('allow_users', ['alice', 'bob'], + 'Users with access.', flag_values=fv) + gflags.DEFINE_spaceseplist('dirs', 'src libs bins', + 'Directories to create.', flag_values=fv) + gflags.DEFINE_multistring('to_delete', ['a.cc', 'b.h'], + 'Files to delete', flag_values=fv) + gflags.DEFINE_multi_int('cols', [5, 7, 23], + 'Columns to select', flag_values=fv) + # Define a few flags in a different module. + module_bar.DefineFlags(flag_values=fv) + # And declare only a few of them to be key. This way, we have + # different kinds of flags, defined in different modules, and not + # all of them are key flags. + gflags.DECLARE_key_flag('tmod_bar_z', flag_values=fv) + gflags.DECLARE_key_flag('tmod_bar_u', flag_values=fv) + + # Generate flag help in XML format in the StringIO sio. + sio = StringIO.StringIO() + fv.WriteHelpInXMLFormat(sio) + + # Check that we got the expected result. + expected_output_template = EXPECTED_HELP_XML_START + main_module_name = gflags._GetMainModule() + module_bar_name = module_bar.__name__ + + if main_module_name < module_bar_name: + expected_output_template += EXPECTED_HELP_XML_FOR_FLAGS_FROM_MAIN_MODULE + expected_output_template += EXPECTED_HELP_XML_FOR_FLAGS_FROM_MODULE_BAR + else: + expected_output_template += EXPECTED_HELP_XML_FOR_FLAGS_FROM_MODULE_BAR + expected_output_template += EXPECTED_HELP_XML_FOR_FLAGS_FROM_MAIN_MODULE + + expected_output_template += EXPECTED_HELP_XML_END + + # XML representation of the whitespace list separators. + whitespace_separators = _ListSeparatorsInXMLFormat(string.whitespace, + indent=' ') + expected_output = ( + expected_output_template % + {'usage_doc': sys.modules['__main__'].__doc__, + 'main_module_name': main_module_name, + 'module_bar_name': module_bar_name, + 'whitespace_separators': whitespace_separators}) + + actual_output = sio.getvalue() + self.assertMultiLineEqual(actual_output, expected_output) + + # Also check that our result is valid XML. minidom.parseString + # throws an xml.parsers.expat.ExpatError in case of an error. + xml.dom.minidom.parseString(actual_output) + + +if __name__ == '__main__': + googletest.main() |