1# Copyright 2017 The Abseil Authors. 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"""Unit tests for the XML-format help generated by the flags.py module.""" 16 17import enum 18import io 19import os 20import string 21import sys 22import xml.dom.minidom 23import xml.sax.saxutils 24 25from absl import flags 26from absl.flags import _helpers 27from absl.flags.tests import module_bar 28from absl.testing import absltest 29 30 31class CreateXMLDOMElement(absltest.TestCase): 32 33 def _check(self, name, value, expected_output): 34 doc = xml.dom.minidom.Document() 35 node = _helpers.create_xml_dom_element(doc, name, value) 36 output = node.toprettyxml(' ', encoding='utf-8') 37 self.assertEqual(expected_output, output) 38 39 def test_create_xml_dom_element(self): 40 self._check('tag', '', b'<tag></tag>\n') 41 self._check('tag', 'plain text', b'<tag>plain text</tag>\n') 42 self._check('tag', '(x < y) && (a >= b)', 43 b'<tag>(x < y) && (a >= b)</tag>\n') 44 45 # If the value is bytes with invalid unicode: 46 bytes_with_invalid_unicodes = b'\x81\xff' 47 # In python 3 the string representation is "b'\x81\xff'" so they are kept 48 # as "b'\x81\xff'". 49 self._check('tag', bytes_with_invalid_unicodes, 50 b"<tag>b'\\x81\\xff'</tag>\n") 51 52 # Some unicode chars are illegal in xml 53 # (http://www.w3.org/TR/REC-xml/#charsets): 54 self._check('tag', u'\x0b\x02\x08\ufffe', b'<tag></tag>\n') 55 56 # Valid unicode will be encoded: 57 self._check('tag', u'\xff', b'<tag>\xc3\xbf</tag>\n') 58 59 60def _list_separators_in_xmlformat(separators, indent=''): 61 """Generates XML encoding of a list of list separators. 62 63 Args: 64 separators: A list of list separators. Usually, this should be a 65 string whose characters are the valid list separators, e.g., ',' 66 means that both comma (',') and space (' ') are valid list 67 separators. 68 indent: A string that is added at the beginning of each generated 69 XML element. 70 71 Returns: 72 A string. 73 """ 74 result = '' 75 separators = list(separators) 76 separators.sort() 77 for sep_char in separators: 78 result += ('%s<list_separator>%s</list_separator>\n' % 79 (indent, repr(sep_char))) 80 return result 81 82 83class FlagCreateXMLDOMElement(absltest.TestCase): 84 """Test the create_xml_dom_element method for a single flag at a time. 85 86 There is one test* method for each kind of DEFINE_* declaration. 87 """ 88 89 def setUp(self): 90 # self.fv is a FlagValues object, just like flags.FLAGS. Each 91 # test registers one flag with this FlagValues. 92 self.fv = flags.FlagValues() 93 94 def _check_flag_help_in_xml(self, flag_name, module_name, 95 expected_output, is_key=False): 96 flag_obj = self.fv[flag_name] 97 doc = xml.dom.minidom.Document() 98 element = flag_obj._create_xml_dom_element(doc, module_name, is_key=is_key) 99 output = element.toprettyxml(indent=' ') 100 self.assertMultiLineEqual(expected_output, output) 101 102 def test_flag_help_in_xml_int(self): 103 flags.DEFINE_integer('index', 17, 'An integer flag', flag_values=self.fv) 104 expected_output_pattern = ( 105 '<flag>\n' 106 ' <file>module.name</file>\n' 107 ' <name>index</name>\n' 108 ' <meaning>An integer flag</meaning>\n' 109 ' <default>17</default>\n' 110 ' <current>%d</current>\n' 111 ' <type>int</type>\n' 112 '</flag>\n') 113 self._check_flag_help_in_xml('index', 'module.name', 114 expected_output_pattern % 17) 115 # Check that the output is correct even when the current value of 116 # a flag is different from the default one. 117 self.fv['index'].value = 20 118 self._check_flag_help_in_xml('index', 'module.name', 119 expected_output_pattern % 20) 120 121 def test_flag_help_in_xml_int_with_bounds(self): 122 flags.DEFINE_integer('nb_iters', 17, 'An integer flag', 123 lower_bound=5, upper_bound=27, 124 flag_values=self.fv) 125 expected_output = ( 126 '<flag>\n' 127 ' <key>yes</key>\n' 128 ' <file>module.name</file>\n' 129 ' <name>nb_iters</name>\n' 130 ' <meaning>An integer flag</meaning>\n' 131 ' <default>17</default>\n' 132 ' <current>17</current>\n' 133 ' <type>int</type>\n' 134 ' <lower_bound>5</lower_bound>\n' 135 ' <upper_bound>27</upper_bound>\n' 136 '</flag>\n') 137 self._check_flag_help_in_xml('nb_iters', 'module.name', expected_output, 138 is_key=True) 139 140 def test_flag_help_in_xml_string(self): 141 flags.DEFINE_string('file_path', '/path/to/my/dir', 'A test string flag.', 142 flag_values=self.fv) 143 expected_output = ( 144 '<flag>\n' 145 ' <file>simple_module</file>\n' 146 ' <name>file_path</name>\n' 147 ' <meaning>A test string flag.</meaning>\n' 148 ' <default>/path/to/my/dir</default>\n' 149 ' <current>/path/to/my/dir</current>\n' 150 ' <type>string</type>\n' 151 '</flag>\n') 152 self._check_flag_help_in_xml('file_path', 'simple_module', expected_output) 153 154 def test_flag_help_in_xml_string_with_xmlillegal_chars(self): 155 flags.DEFINE_string('file_path', '/path/to/\x08my/dir', 156 'A test string flag.', flag_values=self.fv) 157 # '\x08' is not a legal character in XML 1.0 documents. Our 158 # current code purges such characters from the generated XML. 159 expected_output = ( 160 '<flag>\n' 161 ' <file>simple_module</file>\n' 162 ' <name>file_path</name>\n' 163 ' <meaning>A test string flag.</meaning>\n' 164 ' <default>/path/to/my/dir</default>\n' 165 ' <current>/path/to/my/dir</current>\n' 166 ' <type>string</type>\n' 167 '</flag>\n') 168 self._check_flag_help_in_xml('file_path', 'simple_module', expected_output) 169 170 def test_flag_help_in_xml_boolean(self): 171 flags.DEFINE_boolean('use_gpu', False, 'Use gpu for performance.', 172 flag_values=self.fv) 173 expected_output = ( 174 '<flag>\n' 175 ' <key>yes</key>\n' 176 ' <file>a_module</file>\n' 177 ' <name>use_gpu</name>\n' 178 ' <meaning>Use gpu for performance.</meaning>\n' 179 ' <default>false</default>\n' 180 ' <current>false</current>\n' 181 ' <type>bool</type>\n' 182 '</flag>\n') 183 self._check_flag_help_in_xml('use_gpu', 'a_module', expected_output, 184 is_key=True) 185 186 def test_flag_help_in_xml_enum(self): 187 flags.DEFINE_enum('cc_version', 'stable', ['stable', 'experimental'], 188 'Compiler version to use.', flag_values=self.fv) 189 expected_output = ( 190 '<flag>\n' 191 ' <file>tool</file>\n' 192 ' <name>cc_version</name>\n' 193 ' <meaning><stable|experimental>: ' 194 'Compiler version to use.</meaning>\n' 195 ' <default>stable</default>\n' 196 ' <current>stable</current>\n' 197 ' <type>string enum</type>\n' 198 ' <enum_value>stable</enum_value>\n' 199 ' <enum_value>experimental</enum_value>\n' 200 '</flag>\n') 201 self._check_flag_help_in_xml('cc_version', 'tool', expected_output) 202 203 def test_flag_help_in_xml_enum_class(self): 204 class Version(enum.Enum): 205 STABLE = 0 206 EXPERIMENTAL = 1 207 208 flags.DEFINE_enum_class('cc_version', 'STABLE', Version, 209 'Compiler version to use.', flag_values=self.fv) 210 expected_output = ('<flag>\n' 211 ' <file>tool</file>\n' 212 ' <name>cc_version</name>\n' 213 ' <meaning><stable|experimental>: ' 214 'Compiler version to use.</meaning>\n' 215 ' <default>stable</default>\n' 216 ' <current>Version.STABLE</current>\n' 217 ' <type>enum class</type>\n' 218 ' <enum_value>STABLE</enum_value>\n' 219 ' <enum_value>EXPERIMENTAL</enum_value>\n' 220 '</flag>\n') 221 self._check_flag_help_in_xml('cc_version', 'tool', expected_output) 222 223 def test_flag_help_in_xml_comma_separated_list(self): 224 flags.DEFINE_list('files', 'a.cc,a.h,archive/old.zip', 225 'Files to process.', flag_values=self.fv) 226 expected_output = ( 227 '<flag>\n' 228 ' <file>tool</file>\n' 229 ' <name>files</name>\n' 230 ' <meaning>Files to process.</meaning>\n' 231 ' <default>a.cc,a.h,archive/old.zip</default>\n' 232 ' <current>[\'a.cc\', \'a.h\', \'archive/old.zip\']</current>\n' 233 ' <type>comma separated list of strings</type>\n' 234 ' <list_separator>\',\'</list_separator>\n' 235 '</flag>\n') 236 self._check_flag_help_in_xml('files', 'tool', expected_output) 237 238 def test_list_as_default_argument_comma_separated_list(self): 239 flags.DEFINE_list('allow_users', ['alice', 'bob'], 240 'Users with access.', flag_values=self.fv) 241 expected_output = ( 242 '<flag>\n' 243 ' <file>tool</file>\n' 244 ' <name>allow_users</name>\n' 245 ' <meaning>Users with access.</meaning>\n' 246 ' <default>alice,bob</default>\n' 247 ' <current>[\'alice\', \'bob\']</current>\n' 248 ' <type>comma separated list of strings</type>\n' 249 ' <list_separator>\',\'</list_separator>\n' 250 '</flag>\n') 251 self._check_flag_help_in_xml('allow_users', 'tool', expected_output) 252 253 def test_none_as_default_arguments_comma_separated_list(self): 254 flags.DEFINE_list('allow_users', None, 255 'Users with access.', flag_values=self.fv) 256 expected_output = ( 257 '<flag>\n' 258 ' <file>tool</file>\n' 259 ' <name>allow_users</name>\n' 260 ' <meaning>Users with access.</meaning>\n' 261 ' <default></default>\n' 262 ' <current>None</current>\n' 263 ' <type>comma separated list of strings</type>\n' 264 ' <list_separator>\',\'</list_separator>\n' 265 '</flag>\n') 266 self._check_flag_help_in_xml('allow_users', 'tool', expected_output) 267 268 def test_flag_help_in_xml_space_separated_list(self): 269 flags.DEFINE_spaceseplist('dirs', 'src libs bin', 270 'Directories to search.', flag_values=self.fv) 271 expected_separators = sorted(string.whitespace) 272 expected_output = ( 273 '<flag>\n' 274 ' <file>tool</file>\n' 275 ' <name>dirs</name>\n' 276 ' <meaning>Directories to search.</meaning>\n' 277 ' <default>src libs bin</default>\n' 278 ' <current>[\'src\', \'libs\', \'bin\']</current>\n' 279 ' <type>whitespace separated list of strings</type>\n' 280 'LIST_SEPARATORS' 281 '</flag>\n').replace('LIST_SEPARATORS', 282 _list_separators_in_xmlformat(expected_separators, 283 indent=' ')) 284 self._check_flag_help_in_xml('dirs', 'tool', expected_output) 285 286 def test_flag_help_in_xml_space_separated_list_with_comma_compat(self): 287 flags.DEFINE_spaceseplist('dirs', 'src libs,bin', 288 'Directories to search.', comma_compat=True, 289 flag_values=self.fv) 290 expected_separators = sorted(string.whitespace + ',') 291 expected_output = ( 292 '<flag>\n' 293 ' <file>tool</file>\n' 294 ' <name>dirs</name>\n' 295 ' <meaning>Directories to search.</meaning>\n' 296 ' <default>src libs bin</default>\n' 297 ' <current>[\'src\', \'libs\', \'bin\']</current>\n' 298 ' <type>whitespace or comma separated list of strings</type>\n' 299 'LIST_SEPARATORS' 300 '</flag>\n').replace('LIST_SEPARATORS', 301 _list_separators_in_xmlformat(expected_separators, 302 indent=' ')) 303 self._check_flag_help_in_xml('dirs', 'tool', expected_output) 304 305 def test_flag_help_in_xml_multi_string(self): 306 flags.DEFINE_multi_string('to_delete', ['a.cc', 'b.h'], 307 'Files to delete', flag_values=self.fv) 308 expected_output = ( 309 '<flag>\n' 310 ' <file>tool</file>\n' 311 ' <name>to_delete</name>\n' 312 ' <meaning>Files to delete;\n' 313 ' repeat this option to specify a list of values</meaning>\n' 314 ' <default>[\'a.cc\', \'b.h\']</default>\n' 315 ' <current>[\'a.cc\', \'b.h\']</current>\n' 316 ' <type>multi string</type>\n' 317 '</flag>\n') 318 self._check_flag_help_in_xml('to_delete', 'tool', expected_output) 319 320 def test_flag_help_in_xml_multi_int(self): 321 flags.DEFINE_multi_integer('cols', [5, 7, 23], 322 'Columns to select', flag_values=self.fv) 323 expected_output = ( 324 '<flag>\n' 325 ' <file>tool</file>\n' 326 ' <name>cols</name>\n' 327 ' <meaning>Columns to select;\n ' 328 'repeat this option to specify a list of values</meaning>\n' 329 ' <default>[5, 7, 23]</default>\n' 330 ' <current>[5, 7, 23]</current>\n' 331 ' <type>multi int</type>\n' 332 '</flag>\n') 333 self._check_flag_help_in_xml('cols', 'tool', expected_output) 334 335 def test_flag_help_in_xml_multi_enum(self): 336 flags.DEFINE_multi_enum('flavours', ['APPLE', 'BANANA'], 337 ['APPLE', 'BANANA', 'CHERRY'], 338 'Compilation flavour.', flag_values=self.fv) 339 expected_output = ( 340 '<flag>\n' 341 ' <file>tool</file>\n' 342 ' <name>flavours</name>\n' 343 ' <meaning><APPLE|BANANA|CHERRY>: Compilation flavour.;\n' 344 ' repeat this option to specify a list of values</meaning>\n' 345 ' <default>[\'APPLE\', \'BANANA\']</default>\n' 346 ' <current>[\'APPLE\', \'BANANA\']</current>\n' 347 ' <type>multi string enum</type>\n' 348 ' <enum_value>APPLE</enum_value>\n' 349 ' <enum_value>BANANA</enum_value>\n' 350 ' <enum_value>CHERRY</enum_value>\n' 351 '</flag>\n') 352 self._check_flag_help_in_xml('flavours', 'tool', expected_output) 353 354 def test_flag_help_in_xml_multi_enum_class_singleton_default(self): 355 class Fruit(enum.Enum): 356 ORANGE = 0 357 BANANA = 1 358 359 flags.DEFINE_multi_enum_class('fruit', ['ORANGE'], 360 Fruit, 361 'The fruit flag.', flag_values=self.fv) 362 expected_output = ( 363 '<flag>\n' 364 ' <file>tool</file>\n' 365 ' <name>fruit</name>\n' 366 ' <meaning><orange|banana>: The fruit flag.;\n' 367 ' repeat this option to specify a list of values</meaning>\n' 368 ' <default>orange</default>\n' 369 ' <current>orange</current>\n' 370 ' <type>multi enum class</type>\n' 371 ' <enum_value>ORANGE</enum_value>\n' 372 ' <enum_value>BANANA</enum_value>\n' 373 '</flag>\n') 374 self._check_flag_help_in_xml('fruit', 'tool', expected_output) 375 376 def test_flag_help_in_xml_multi_enum_class_list_default(self): 377 class Fruit(enum.Enum): 378 ORANGE = 0 379 BANANA = 1 380 381 flags.DEFINE_multi_enum_class('fruit', ['ORANGE', 'BANANA'], 382 Fruit, 383 'The fruit flag.', flag_values=self.fv) 384 expected_output = ( 385 '<flag>\n' 386 ' <file>tool</file>\n' 387 ' <name>fruit</name>\n' 388 ' <meaning><orange|banana>: The fruit flag.;\n' 389 ' repeat this option to specify a list of values</meaning>\n' 390 ' <default>orange,banana</default>\n' 391 ' <current>orange,banana</current>\n' 392 ' <type>multi enum class</type>\n' 393 ' <enum_value>ORANGE</enum_value>\n' 394 ' <enum_value>BANANA</enum_value>\n' 395 '</flag>\n') 396 self._check_flag_help_in_xml('fruit', 'tool', expected_output) 397 398# The next EXPECTED_HELP_XML_* constants are parts of a template for 399# the expected XML output from WriteHelpInXMLFormatTest below. When 400# we assemble these parts into a single big string, we'll take into 401# account the ordering between the name of the main module and the 402# name of module_bar. Next, we'll fill in the docstring for this 403# module (%(usage_doc)s), the name of the main module 404# (%(main_module_name)s) and the name of the module module_bar 405# (%(module_bar_name)s). See WriteHelpInXMLFormatTest below. 406EXPECTED_HELP_XML_START = """\ 407<?xml version="1.0" encoding="utf-8"?> 408<AllFlags> 409 <program>%(basename_of_argv0)s</program> 410 <usage>%(usage_doc)s</usage> 411""" 412 413EXPECTED_HELP_XML_FOR_FLAGS_FROM_MAIN_MODULE = """\ 414 <flag> 415 <key>yes</key> 416 <file>%(main_module_name)s</file> 417 <name>allow_users</name> 418 <meaning>Users with access.</meaning> 419 <default>alice,bob</default> 420 <current>['alice', 'bob']</current> 421 <type>comma separated list of strings</type> 422 <list_separator>','</list_separator> 423 </flag> 424 <flag> 425 <key>yes</key> 426 <file>%(main_module_name)s</file> 427 <name>cc_version</name> 428 <meaning><stable|experimental>: Compiler version to use.</meaning> 429 <default>stable</default> 430 <current>stable</current> 431 <type>string enum</type> 432 <enum_value>stable</enum_value> 433 <enum_value>experimental</enum_value> 434 </flag> 435 <flag> 436 <key>yes</key> 437 <file>%(main_module_name)s</file> 438 <name>cols</name> 439 <meaning>Columns to select; 440 repeat this option to specify a list of values</meaning> 441 <default>[5, 7, 23]</default> 442 <current>[5, 7, 23]</current> 443 <type>multi int</type> 444 </flag> 445 <flag> 446 <key>yes</key> 447 <file>%(main_module_name)s</file> 448 <name>dirs</name> 449 <meaning>Directories to create.</meaning> 450 <default>src libs bins</default> 451 <current>['src', 'libs', 'bins']</current> 452 <type>whitespace separated list of strings</type> 453%(whitespace_separators)s </flag> 454 <flag> 455 <key>yes</key> 456 <file>%(main_module_name)s</file> 457 <name>file_path</name> 458 <meaning>A test string flag.</meaning> 459 <default>/path/to/my/dir</default> 460 <current>/path/to/my/dir</current> 461 <type>string</type> 462 </flag> 463 <flag> 464 <key>yes</key> 465 <file>%(main_module_name)s</file> 466 <name>files</name> 467 <meaning>Files to process.</meaning> 468 <default>a.cc,a.h,archive/old.zip</default> 469 <current>['a.cc', 'a.h', 'archive/old.zip']</current> 470 <type>comma separated list of strings</type> 471 <list_separator>\',\'</list_separator> 472 </flag> 473 <flag> 474 <key>yes</key> 475 <file>%(main_module_name)s</file> 476 <name>flavours</name> 477 <meaning><APPLE|BANANA|CHERRY>: Compilation flavour.; 478 repeat this option to specify a list of values</meaning> 479 <default>['APPLE', 'BANANA']</default> 480 <current>['APPLE', 'BANANA']</current> 481 <type>multi string enum</type> 482 <enum_value>APPLE</enum_value> 483 <enum_value>BANANA</enum_value> 484 <enum_value>CHERRY</enum_value> 485 </flag> 486 <flag> 487 <key>yes</key> 488 <file>%(main_module_name)s</file> 489 <name>index</name> 490 <meaning>An integer flag</meaning> 491 <default>17</default> 492 <current>17</current> 493 <type>int</type> 494 </flag> 495 <flag> 496 <key>yes</key> 497 <file>%(main_module_name)s</file> 498 <name>nb_iters</name> 499 <meaning>An integer flag</meaning> 500 <default>17</default> 501 <current>17</current> 502 <type>int</type> 503 <lower_bound>5</lower_bound> 504 <upper_bound>27</upper_bound> 505 </flag> 506 <flag> 507 <key>yes</key> 508 <file>%(main_module_name)s</file> 509 <name>to_delete</name> 510 <meaning>Files to delete; 511 repeat this option to specify a list of values</meaning> 512 <default>['a.cc', 'b.h']</default> 513 <current>['a.cc', 'b.h']</current> 514 <type>multi string</type> 515 </flag> 516 <flag> 517 <key>yes</key> 518 <file>%(main_module_name)s</file> 519 <name>use_gpu</name> 520 <meaning>Use gpu for performance.</meaning> 521 <default>false</default> 522 <current>false</current> 523 <type>bool</type> 524 </flag> 525""" 526 527EXPECTED_HELP_XML_FOR_FLAGS_FROM_MODULE_BAR = """\ 528 <flag> 529 <file>%(module_bar_name)s</file> 530 <name>tmod_bar_t</name> 531 <meaning>Sample int flag.</meaning> 532 <default>4</default> 533 <current>4</current> 534 <type>int</type> 535 </flag> 536 <flag> 537 <key>yes</key> 538 <file>%(module_bar_name)s</file> 539 <name>tmod_bar_u</name> 540 <meaning>Sample int flag.</meaning> 541 <default>5</default> 542 <current>5</current> 543 <type>int</type> 544 </flag> 545 <flag> 546 <file>%(module_bar_name)s</file> 547 <name>tmod_bar_v</name> 548 <meaning>Sample int flag.</meaning> 549 <default>6</default> 550 <current>6</current> 551 <type>int</type> 552 </flag> 553 <flag> 554 <file>%(module_bar_name)s</file> 555 <name>tmod_bar_x</name> 556 <meaning>Boolean flag.</meaning> 557 <default>true</default> 558 <current>true</current> 559 <type>bool</type> 560 </flag> 561 <flag> 562 <file>%(module_bar_name)s</file> 563 <name>tmod_bar_y</name> 564 <meaning>String flag.</meaning> 565 <default>default</default> 566 <current>default</current> 567 <type>string</type> 568 </flag> 569 <flag> 570 <key>yes</key> 571 <file>%(module_bar_name)s</file> 572 <name>tmod_bar_z</name> 573 <meaning>Another boolean flag from module bar.</meaning> 574 <default>false</default> 575 <current>false</current> 576 <type>bool</type> 577 </flag> 578""" 579 580EXPECTED_HELP_XML_END = """\ 581</AllFlags> 582""" 583 584 585class WriteHelpInXMLFormatTest(absltest.TestCase): 586 """Big test of FlagValues.write_help_in_xml_format, with several flags.""" 587 588 def test_write_help_in_xmlformat(self): 589 fv = flags.FlagValues() 590 # Since these flags are defined by the top module, they are all key. 591 flags.DEFINE_integer('index', 17, 'An integer flag', flag_values=fv) 592 flags.DEFINE_integer('nb_iters', 17, 'An integer flag', 593 lower_bound=5, upper_bound=27, flag_values=fv) 594 flags.DEFINE_string('file_path', '/path/to/my/dir', 'A test string flag.', 595 flag_values=fv) 596 flags.DEFINE_boolean('use_gpu', False, 'Use gpu for performance.', 597 flag_values=fv) 598 flags.DEFINE_enum('cc_version', 'stable', ['stable', 'experimental'], 599 'Compiler version to use.', flag_values=fv) 600 flags.DEFINE_list('files', 'a.cc,a.h,archive/old.zip', 601 'Files to process.', flag_values=fv) 602 flags.DEFINE_list('allow_users', ['alice', 'bob'], 603 'Users with access.', flag_values=fv) 604 flags.DEFINE_spaceseplist('dirs', 'src libs bins', 605 'Directories to create.', flag_values=fv) 606 flags.DEFINE_multi_string('to_delete', ['a.cc', 'b.h'], 607 'Files to delete', flag_values=fv) 608 flags.DEFINE_multi_integer('cols', [5, 7, 23], 609 'Columns to select', flag_values=fv) 610 flags.DEFINE_multi_enum('flavours', ['APPLE', 'BANANA'], 611 ['APPLE', 'BANANA', 'CHERRY'], 612 'Compilation flavour.', flag_values=fv) 613 # Define a few flags in a different module. 614 module_bar.define_flags(flag_values=fv) 615 # And declare only a few of them to be key. This way, we have 616 # different kinds of flags, defined in different modules, and not 617 # all of them are key flags. 618 flags.declare_key_flag('tmod_bar_z', flag_values=fv) 619 flags.declare_key_flag('tmod_bar_u', flag_values=fv) 620 621 # Generate flag help in XML format in the StringIO sio. 622 sio = io.StringIO() 623 fv.write_help_in_xml_format(sio) 624 625 # Check that we got the expected result. 626 expected_output_template = EXPECTED_HELP_XML_START 627 main_module_name = sys.argv[0] 628 module_bar_name = module_bar.__name__ 629 630 if main_module_name < module_bar_name: 631 expected_output_template += EXPECTED_HELP_XML_FOR_FLAGS_FROM_MAIN_MODULE 632 expected_output_template += EXPECTED_HELP_XML_FOR_FLAGS_FROM_MODULE_BAR 633 else: 634 expected_output_template += EXPECTED_HELP_XML_FOR_FLAGS_FROM_MODULE_BAR 635 expected_output_template += EXPECTED_HELP_XML_FOR_FLAGS_FROM_MAIN_MODULE 636 637 expected_output_template += EXPECTED_HELP_XML_END 638 639 # XML representation of the whitespace list separators. 640 whitespace_separators = _list_separators_in_xmlformat(string.whitespace, 641 indent=' ') 642 expected_output = ( 643 expected_output_template % 644 {'basename_of_argv0': os.path.basename(sys.argv[0]), 645 'usage_doc': sys.modules['__main__'].__doc__, 646 'main_module_name': main_module_name, 647 'module_bar_name': module_bar_name, 648 'whitespace_separators': whitespace_separators}) 649 650 actual_output = sio.getvalue() 651 self.assertMultiLineEqual(expected_output, actual_output) 652 653 # Also check that our result is valid XML. minidom.parseString 654 # throws an xml.parsers.expat.ExpatError in case of an error. 655 xml.dom.minidom.parseString(actual_output) 656 657 658if __name__ == '__main__': 659 absltest.main() 660