3
# Copyright (c) 2009, Google Inc.
6
# Redistribution and use in source and binary forms, with or without
7
# modification, are permitted provided that the following conditions are
10
# * Redistributions of source code must retain the above copyright
11
# notice, this list of conditions and the following disclaimer.
12
# * Redistributions in binary form must reproduce the above
13
# copyright notice, this list of conditions and the following disclaimer
14
# in the documentation and/or other materials provided with the
16
# * Neither the name of Google Inc. nor the names of its
17
# contributors may be used to endorse or promote products derived from
18
# this software without specific prior written permission.
20
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
"""Unit tests for the XML-format help generated by the gflags.py module."""
34
__author__ = 'Alex Salcianu'
41
import xml.dom.minidom
42
import xml.sax.saxutils
44
# We use the name 'flags' internally in this test, for historical reasons.
45
# Don't do this yourself! :-) Just do 'import gflags; FLAGS=gflags.FLAGS; etc'
46
import gflags as flags
48
# For historic reasons, we use the name module_bar instead of test_module_bar
49
import test_module_bar as module_bar
51
def MultiLineEqual(expected_help, help):
52
"""Returns True if expected_help == help. Otherwise returns False
53
and logs the difference in a human-readable way.
55
if help == expected_help:
58
print "Error: FLAGS.MainModuleHelp() didn't return the expected result."
63
help_lines = help.split('\n')
64
expected_help_lines = expected_help.split('\n')
66
num_help_lines = len(help_lines)
67
num_expected_help_lines = len(expected_help_lines)
69
if num_help_lines != num_expected_help_lines:
70
print "Number of help lines = %d, expected %d" % (
71
num_help_lines, num_expected_help_lines)
73
num_to_match = min(num_help_lines, num_expected_help_lines)
75
for i in range(num_to_match):
76
if help_lines[i] != expected_help_lines[i]:
77
print "One discrepancy: Got:"
80
print expected_help_lines[i]
83
# If we got here, found no discrepancy, print first new line.
84
if num_help_lines > num_expected_help_lines:
85
print "New help line:"
86
print help_lines[num_expected_help_lines]
87
elif num_expected_help_lines > num_help_lines:
88
print "Missing expected help line:"
89
print expected_help_lines[num_help_lines]
91
print "Bug in this test -- discrepancy detected but not found."
96
class _MakeXMLSafeTest(unittest.TestCase):
98
def _Check(self, s, expected_output):
99
self.assertEqual(flags._MakeXMLSafe(s), expected_output)
101
def testMakeXMLSafe(self):
102
self._Check('plain text', 'plain text')
103
self._Check('(x < y) && (a >= b)',
104
'(x < y) && (a >= b)')
105
# Some characters with ASCII code < 32 are illegal in XML 1.0 and
106
# are removed by us. However, '\n', '\t', and '\r' are legal.
107
self._Check('\x09\x0btext \x02 with\x0dsome \x08 good & bad chars',
108
'\ttext with\rsome good & bad chars')
111
def _ListSeparatorsInXMLFormat(separators, indent=''):
112
"""Generates XML encoding of a list of list separators.
115
separators: A list of list separators. Usually, this should be a
116
string whose characters are the valid list separators, e.g., ','
117
means that both comma (',') and space (' ') are valid list
119
indent: A string that is added at the beginning of each generated
126
separators = list(separators)
128
for sep_char in separators:
129
result += ('%s<list_separator>%s</list_separator>\n' %
130
(indent, repr(sep_char)))
134
class WriteFlagHelpInXMLFormatTest(unittest.TestCase):
135
"""Test the XML-format help for a single flag at a time.
137
There is one test* method for each kind of DEFINE_* declaration.
141
# self.fv is a FlagValues object, just like flags.FLAGS. Each
142
# test registers one flag with this FlagValues.
143
self.fv = flags.FlagValues()
145
def assertMultiLineEqual(self, expected, actual):
146
self.assert_(MultiLineEqual(expected, actual))
148
def _CheckFlagHelpInXML(self, flag_name, module_name,
149
expected_output, is_key=False):
150
# StringIO.StringIO is a file object that writes into a memory string.
151
sio = StringIO.StringIO()
152
flag_obj = self.fv[flag_name]
153
flag_obj.WriteInfoInXMLFormat(sio, module_name, is_key=is_key, indent=' ')
154
self.assertMultiLineEqual(sio.getvalue(), expected_output)
157
def testFlagHelpInXML_Int(self):
158
flags.DEFINE_integer('index', 17, 'An integer flag', flag_values=self.fv)
159
expected_output_pattern = (
161
' <file>module.name</file>\n'
162
' <name>index</name>\n'
163
' <meaning>An integer flag</meaning>\n'
164
' <default>17</default>\n'
165
' <current>%d</current>\n'
166
' <type>int</type>\n'
168
self._CheckFlagHelpInXML('index', 'module.name',
169
expected_output_pattern % 17)
170
# Check that the output is correct even when the current value of
171
# a flag is different from the default one.
172
self.fv['index'].value = 20
173
self._CheckFlagHelpInXML('index', 'module.name',
174
expected_output_pattern % 20)
176
def testFlagHelpInXML_IntWithBounds(self):
177
flags.DEFINE_integer('nb_iters', 17, 'An integer flag',
178
lower_bound=5, upper_bound=27,
183
' <file>module.name</file>\n'
184
' <name>nb_iters</name>\n'
185
' <meaning>An integer flag</meaning>\n'
186
' <default>17</default>\n'
187
' <current>17</current>\n'
188
' <type>int</type>\n'
189
' <lower_bound>5</lower_bound>\n'
190
' <upper_bound>27</upper_bound>\n'
192
self._CheckFlagHelpInXML('nb_iters', 'module.name',
193
expected_output, is_key=True)
195
def testFlagHelpInXML_String(self):
196
flags.DEFINE_string('file_path', '/path/to/my/dir', 'A test string flag.',
200
' <file>simple_module</file>\n'
201
' <name>file_path</name>\n'
202
' <meaning>A test string flag.</meaning>\n'
203
' <default>/path/to/my/dir</default>\n'
204
' <current>/path/to/my/dir</current>\n'
205
' <type>string</type>\n'
207
self._CheckFlagHelpInXML('file_path', 'simple_module',
210
def testFlagHelpInXML_StringWithXMLIllegalChars(self):
211
flags.DEFINE_string('file_path', '/path/to/\x08my/dir',
212
'A test string flag.', flag_values=self.fv)
213
# '\x08' is not a legal character in XML 1.0 documents. Our
214
# current code purges such characters from the generated XML.
217
' <file>simple_module</file>\n'
218
' <name>file_path</name>\n'
219
' <meaning>A test string flag.</meaning>\n'
220
' <default>/path/to/my/dir</default>\n'
221
' <current>/path/to/my/dir</current>\n'
222
' <type>string</type>\n'
224
self._CheckFlagHelpInXML('file_path', 'simple_module',
227
def testFlagHelpInXML_Boolean(self):
228
flags.DEFINE_boolean('use_hack', False, 'Use performance hack',
233
' <file>a_module</file>\n'
234
' <name>use_hack</name>\n'
235
' <meaning>Use performance hack</meaning>\n'
236
' <default>false</default>\n'
237
' <current>false</current>\n'
238
' <type>bool</type>\n'
240
self._CheckFlagHelpInXML('use_hack', 'a_module',
241
expected_output, is_key=True)
243
def testFlagHelpInXML_Enum(self):
244
flags.DEFINE_enum('cc_version', 'stable', ['stable', 'experimental'],
245
'Compiler version to use.', flag_values=self.fv)
248
' <file>tool</file>\n'
249
' <name>cc_version</name>\n'
250
' <meaning><stable|experimental>: '
251
'Compiler version to use.</meaning>\n'
252
' <default>stable</default>\n'
253
' <current>stable</current>\n'
254
' <type>string enum</type>\n'
255
' <enum_value>stable</enum_value>\n'
256
' <enum_value>experimental</enum_value>\n'
258
self._CheckFlagHelpInXML('cc_version', 'tool', expected_output)
260
def testFlagHelpInXML_CommaSeparatedList(self):
261
flags.DEFINE_list('files', 'a.cc,a.h,archive/old.zip',
262
'Files to process.', flag_values=self.fv)
265
' <file>tool</file>\n'
266
' <name>files</name>\n'
267
' <meaning>Files to process.</meaning>\n'
268
' <default>a.cc,a.h,archive/old.zip</default>\n'
269
' <current>[\'a.cc\', \'a.h\', \'archive/old.zip\']</current>\n'
270
' <type>comma separated list of strings</type>\n'
271
' <list_separator>\',\'</list_separator>\n'
273
self._CheckFlagHelpInXML('files', 'tool', expected_output)
275
def testFlagHelpInXML_SpaceSeparatedList(self):
276
flags.DEFINE_spaceseplist('dirs', 'src libs bin',
277
'Directories to search.', flag_values=self.fv)
280
' <file>tool</file>\n'
281
' <name>dirs</name>\n'
282
' <meaning>Directories to search.</meaning>\n'
283
' <default>src libs bin</default>\n'
284
' <current>[\'src\', \'libs\', \'bin\']</current>\n'
285
' <type>whitespace separated list of strings</type>\n'
287
' </flag>\n').replace('LIST_SEPARATORS',
288
_ListSeparatorsInXMLFormat(string.whitespace,
290
self._CheckFlagHelpInXML('dirs', 'tool', expected_output)
292
def testFlagHelpInXML_MultiString(self):
293
flags.DEFINE_multistring('to_delete', ['a.cc', 'b.h'],
294
'Files to delete', flag_values=self.fv)
297
' <file>tool</file>\n'
298
' <name>to_delete</name>\n'
299
' <meaning>Files to delete;\n '
300
'repeat this option to specify a list of values</meaning>\n'
301
' <default>[\'a.cc\', \'b.h\']</default>\n'
302
' <current>[\'a.cc\', \'b.h\']</current>\n'
303
' <type>multi string</type>\n'
305
self._CheckFlagHelpInXML('to_delete', 'tool', expected_output)
307
def testFlagHelpInXML_MultiInt(self):
308
flags.DEFINE_multi_int('cols', [5, 7, 23],
309
'Columns to select', flag_values=self.fv)
312
' <file>tool</file>\n'
313
' <name>cols</name>\n'
314
' <meaning>Columns to select;\n '
315
'repeat this option to specify a list of values</meaning>\n'
316
' <default>[5, 7, 23]</default>\n'
317
' <current>[5, 7, 23]</current>\n'
318
' <type>multi int</type>\n'
320
self._CheckFlagHelpInXML('cols', 'tool', expected_output)
323
# The next EXPECTED_HELP_XML_* constants are parts of a template for
324
# the expected XML output from WriteHelpInXMLFormatTest below. When
325
# we assemble these parts into a single big string, we'll take into
326
# account the ordering between the name of the main module and the
327
# name of module_bar. Next, we'll fill in the docstring for this
328
# module (%(usage_doc)s), the name of the main module
329
# (%(main_module_name)s) and the name of the module module_bar
330
# (%(module_bar_name)s). See WriteHelpInXMLFormatTest below.
332
# NOTE: given the current implementation of _GetMainModule(), we
333
# already know the ordering between the main module and module_bar.
334
# However, there is no guarantee that _GetMainModule will never be
335
# changed in the future (especially since it's far from perfect).
336
EXPECTED_HELP_XML_START = """\
337
<?xml version="1.0"?>
339
<program>gflags_helpxml_test.py</program>
340
<usage>%(usage_doc)s</usage>
343
EXPECTED_HELP_XML_FOR_FLAGS_FROM_MAIN_MODULE = """\
346
<file>%(main_module_name)s</file>
347
<name>cc_version</name>
348
<meaning><stable|experimental>: Compiler version to use.</meaning>
349
<default>stable</default>
350
<current>stable</current>
351
<type>string enum</type>
352
<enum_value>stable</enum_value>
353
<enum_value>experimental</enum_value>
357
<file>%(main_module_name)s</file>
359
<meaning>Columns to select;
360
repeat this option to specify a list of values</meaning>
361
<default>[5, 7, 23]</default>
362
<current>[5, 7, 23]</current>
363
<type>multi int</type>
367
<file>%(main_module_name)s</file>
369
<meaning>Directories to create.</meaning>
370
<default>src libs bins</default>
371
<current>['src', 'libs', 'bins']</current>
372
<type>whitespace separated list of strings</type>
373
%(whitespace_separators)s </flag>
376
<file>%(main_module_name)s</file>
377
<name>file_path</name>
378
<meaning>A test string flag.</meaning>
379
<default>/path/to/my/dir</default>
380
<current>/path/to/my/dir</current>
385
<file>%(main_module_name)s</file>
387
<meaning>Files to process.</meaning>
388
<default>a.cc,a.h,archive/old.zip</default>
389
<current>['a.cc', 'a.h', 'archive/old.zip']</current>
390
<type>comma separated list of strings</type>
391
<list_separator>\',\'</list_separator>
395
<file>%(main_module_name)s</file>
397
<meaning>An integer flag</meaning>
398
<default>17</default>
399
<current>17</current>
404
<file>%(main_module_name)s</file>
405
<name>nb_iters</name>
406
<meaning>An integer flag</meaning>
407
<default>17</default>
408
<current>17</current>
410
<lower_bound>5</lower_bound>
411
<upper_bound>27</upper_bound>
415
<file>%(main_module_name)s</file>
416
<name>to_delete</name>
417
<meaning>Files to delete;
418
repeat this option to specify a list of values</meaning>
419
<default>['a.cc', 'b.h']</default>
420
<current>['a.cc', 'b.h']</current>
421
<type>multi string</type>
425
<file>%(main_module_name)s</file>
426
<name>use_hack</name>
427
<meaning>Use performance hack</meaning>
428
<default>false</default>
429
<current>false</current>
434
EXPECTED_HELP_XML_FOR_FLAGS_FROM_MODULE_BAR = """\
436
<file>%(module_bar_name)s</file>
437
<name>tmod_bar_t</name>
438
<meaning>Sample int flag.</meaning>
445
<file>%(module_bar_name)s</file>
446
<name>tmod_bar_u</name>
447
<meaning>Sample int flag.</meaning>
453
<file>%(module_bar_name)s</file>
454
<name>tmod_bar_v</name>
455
<meaning>Sample int flag.</meaning>
461
<file>%(module_bar_name)s</file>
462
<name>tmod_bar_x</name>
463
<meaning>Boolean flag.</meaning>
464
<default>true</default>
465
<current>true</current>
469
<file>%(module_bar_name)s</file>
470
<name>tmod_bar_y</name>
471
<meaning>String flag.</meaning>
472
<default>default</default>
473
<current>default</current>
478
<file>%(module_bar_name)s</file>
479
<name>tmod_bar_z</name>
480
<meaning>Another boolean flag from module bar.</meaning>
481
<default>false</default>
482
<current>false</current>
487
EXPECTED_HELP_XML_END = """\
492
class WriteHelpInXMLFormatTest(unittest.TestCase):
493
"""Big test of FlagValues.WriteHelpInXMLFormat, with several flags."""
495
def assertMultiLineEqual(self, expected, actual):
496
self.assert_(MultiLineEqual(expected, actual))
498
def testWriteHelpInXMLFormat(self):
499
fv = flags.FlagValues()
500
# Since these flags are defined by the top module, they are all key.
501
flags.DEFINE_integer('index', 17, 'An integer flag', flag_values=fv)
502
flags.DEFINE_integer('nb_iters', 17, 'An integer flag',
503
lower_bound=5, upper_bound=27, flag_values=fv)
504
flags.DEFINE_string('file_path', '/path/to/my/dir', 'A test string flag.',
506
flags.DEFINE_boolean('use_hack', False, 'Use performance hack',
508
flags.DEFINE_enum('cc_version', 'stable', ['stable', 'experimental'],
509
'Compiler version to use.', flag_values=fv)
510
flags.DEFINE_list('files', 'a.cc,a.h,archive/old.zip',
511
'Files to process.', flag_values=fv)
512
flags.DEFINE_spaceseplist('dirs', 'src libs bins',
513
'Directories to create.', flag_values=fv)
514
flags.DEFINE_multistring('to_delete', ['a.cc', 'b.h'],
515
'Files to delete', flag_values=fv)
516
flags.DEFINE_multi_int('cols', [5, 7, 23],
517
'Columns to select', flag_values=fv)
518
# Define a few flags in a different module.
519
module_bar.DefineFlags(flag_values=fv)
520
# And declare only a few of them to be key. This way, we have
521
# different kinds of flags, defined in different modules, and not
522
# all of them are key flags.
523
flags.DECLARE_key_flag('tmod_bar_z', flag_values=fv)
524
flags.DECLARE_key_flag('tmod_bar_u', flag_values=fv)
526
# Generate flag help in XML format in the StringIO sio.
527
sio = StringIO.StringIO()
528
fv.WriteHelpInXMLFormat(sio)
530
# Check that we got the expected result.
531
expected_output_template = EXPECTED_HELP_XML_START
532
main_module_name = flags._GetMainModule()
533
module_bar_name = module_bar.__name__
535
if main_module_name < module_bar_name:
536
expected_output_template += EXPECTED_HELP_XML_FOR_FLAGS_FROM_MAIN_MODULE
537
expected_output_template += EXPECTED_HELP_XML_FOR_FLAGS_FROM_MODULE_BAR
539
expected_output_template += EXPECTED_HELP_XML_FOR_FLAGS_FROM_MODULE_BAR
540
expected_output_template += EXPECTED_HELP_XML_FOR_FLAGS_FROM_MAIN_MODULE
542
expected_output_template += EXPECTED_HELP_XML_END
544
# XML representation of the whitespace list separators.
545
whitespace_separators = _ListSeparatorsInXMLFormat(string.whitespace,
548
expected_output_template %
549
{'usage_doc': sys.modules['__main__'].__doc__,
550
'main_module_name': main_module_name,
551
'module_bar_name': module_bar_name,
552
'whitespace_separators': whitespace_separators})
554
actual_output = sio.getvalue()
555
self.assertMultiLineEqual(actual_output, expected_output)
557
# Also check that our result is valid XML. minidom.parseString
558
# throws an xml.parsers.expat.ExpatError in case of an error.
559
xml.dom.minidom.parseString(actual_output)
562
if __name__ == '__main__':