| # Copyright 2018 The Abseil Authors. |
| # |
| # 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 absl.flags.argparse_flags.""" |
| |
| import io |
| import os |
| import subprocess |
| import sys |
| import tempfile |
| from unittest import mock |
| |
| from absl import flags |
| from absl import logging |
| from absl.flags import argparse_flags |
| from absl.testing import _bazelize_command |
| from absl.testing import absltest |
| from absl.testing import parameterized |
| |
| |
| class ArgparseFlagsTest(parameterized.TestCase): |
| |
| def setUp(self): |
| super().setUp() |
| self._absl_flags = flags.FlagValues() |
| flags.DEFINE_bool( |
| 'absl_bool', None, 'help for --absl_bool.', |
| short_name='b', flag_values=self._absl_flags) |
| # Add a boolean flag that starts with "no", to verify it can correctly |
| # handle the "no" prefixes in boolean flags. |
| flags.DEFINE_bool( |
| 'notice', None, 'help for --notice.', |
| flag_values=self._absl_flags) |
| flags.DEFINE_string( |
| 'absl_string', 'default', 'help for --absl_string=%.', |
| short_name='s', flag_values=self._absl_flags) |
| flags.DEFINE_integer( |
| 'absl_integer', 1, 'help for --absl_integer.', |
| flag_values=self._absl_flags) |
| flags.DEFINE_float( |
| 'absl_float', 1, 'help for --absl_integer.', |
| flag_values=self._absl_flags) |
| flags.DEFINE_enum( |
| 'absl_enum', 'apple', ['apple', 'orange'], 'help for --absl_enum.', |
| flag_values=self._absl_flags) |
| |
| def test_dash_as_prefix_char_only(self): |
| with self.assertRaises(ValueError): |
| argparse_flags.ArgumentParser(prefix_chars='/') |
| |
| def test_default_inherited_absl_flags_value(self): |
| parser = argparse_flags.ArgumentParser() |
| self.assertIs(parser._inherited_absl_flags, flags.FLAGS) |
| |
| def test_parse_absl_flags(self): |
| parser = argparse_flags.ArgumentParser( |
| inherited_absl_flags=self._absl_flags) |
| self.assertFalse(self._absl_flags.is_parsed()) |
| self.assertTrue(self._absl_flags['absl_string'].using_default_value) |
| self.assertTrue(self._absl_flags['absl_integer'].using_default_value) |
| self.assertTrue(self._absl_flags['absl_float'].using_default_value) |
| self.assertTrue(self._absl_flags['absl_enum'].using_default_value) |
| |
| parser.parse_args( |
| ['--absl_string=new_string', '--absl_integer', '2']) |
| self.assertEqual(self._absl_flags.absl_string, 'new_string') |
| self.assertEqual(self._absl_flags.absl_integer, 2) |
| self.assertTrue(self._absl_flags.is_parsed()) |
| self.assertFalse(self._absl_flags['absl_string'].using_default_value) |
| self.assertFalse(self._absl_flags['absl_integer'].using_default_value) |
| self.assertTrue(self._absl_flags['absl_float'].using_default_value) |
| self.assertTrue(self._absl_flags['absl_enum'].using_default_value) |
| |
| @parameterized.named_parameters( |
| ('true', ['--absl_bool'], True), |
| ('false', ['--noabsl_bool'], False), |
| ('does_not_accept_equal_value', ['--absl_bool=true'], SystemExit), |
| ('does_not_accept_space_value', ['--absl_bool', 'true'], SystemExit), |
| ('long_name_single_dash', ['-absl_bool'], SystemExit), |
| ('short_name', ['-b'], True), |
| ('short_name_false', ['-nob'], SystemExit), |
| ('short_name_double_dash', ['--b'], SystemExit), |
| ('short_name_double_dash_false', ['--nob'], SystemExit), |
| ) |
| def test_parse_boolean_flags(self, args, expected): |
| parser = argparse_flags.ArgumentParser( |
| inherited_absl_flags=self._absl_flags) |
| self.assertIsNone(self._absl_flags['absl_bool'].value) |
| self.assertIsNone(self._absl_flags['b'].value) |
| if isinstance(expected, bool): |
| parser.parse_args(args) |
| self.assertEqual(expected, self._absl_flags.absl_bool) |
| self.assertEqual(expected, self._absl_flags.b) |
| else: |
| with self.assertRaises(expected): |
| parser.parse_args(args) |
| |
| @parameterized.named_parameters( |
| ('true', ['--notice'], True), |
| ('false', ['--nonotice'], False), |
| ) |
| def test_parse_boolean_existing_no_prefix(self, args, expected): |
| parser = argparse_flags.ArgumentParser( |
| inherited_absl_flags=self._absl_flags) |
| self.assertIsNone(self._absl_flags['notice'].value) |
| parser.parse_args(args) |
| self.assertEqual(expected, self._absl_flags.notice) |
| |
| def test_unrecognized_flag(self): |
| parser = argparse_flags.ArgumentParser( |
| inherited_absl_flags=self._absl_flags) |
| with self.assertRaises(SystemExit): |
| parser.parse_args(['--unknown_flag=what']) |
| |
| def test_absl_validators(self): |
| |
| @flags.validator('absl_integer', flag_values=self._absl_flags) |
| def ensure_positive(value): |
| return value > 0 |
| |
| parser = argparse_flags.ArgumentParser( |
| inherited_absl_flags=self._absl_flags) |
| with self.assertRaises(SystemExit): |
| parser.parse_args(['--absl_integer', '-2']) |
| |
| del ensure_positive |
| |
| @parameterized.named_parameters( |
| ('regular_name_double_dash', '--absl_string=new_string', 'new_string'), |
| ('regular_name_single_dash', '-absl_string=new_string', SystemExit), |
| ('short_name_double_dash', '--s=new_string', SystemExit), |
| ('short_name_single_dash', '-s=new_string', 'new_string'), |
| ) |
| def test_dashes(self, argument, expected): |
| parser = argparse_flags.ArgumentParser( |
| inherited_absl_flags=self._absl_flags) |
| if isinstance(expected, str): |
| parser.parse_args([argument]) |
| self.assertEqual(self._absl_flags.absl_string, expected) |
| else: |
| with self.assertRaises(expected): |
| parser.parse_args([argument]) |
| |
| def test_absl_flags_not_added_to_namespace(self): |
| parser = argparse_flags.ArgumentParser( |
| inherited_absl_flags=self._absl_flags) |
| args = parser.parse_args(['--absl_string=new_string']) |
| self.assertIsNone(getattr(args, 'absl_string', None)) |
| |
| def test_mixed_flags_and_positional(self): |
| parser = argparse_flags.ArgumentParser( |
| inherited_absl_flags=self._absl_flags) |
| parser.add_argument('--header', help='Header message to print.') |
| parser.add_argument('integers', metavar='N', type=int, nargs='+', |
| help='an integer for the accumulator') |
| |
| args = parser.parse_args( |
| ['--absl_string=new_string', '--header=HEADER', '--absl_integer', |
| '2', '3', '4']) |
| self.assertEqual(self._absl_flags.absl_string, 'new_string') |
| self.assertEqual(self._absl_flags.absl_integer, 2) |
| self.assertEqual(args.header, 'HEADER') |
| self.assertListEqual(args.integers, [3, 4]) |
| |
| def test_subparsers(self): |
| parser = argparse_flags.ArgumentParser( |
| inherited_absl_flags=self._absl_flags) |
| parser.add_argument('--header', help='Header message to print.') |
| subparsers = parser.add_subparsers(help='The command to execute.') |
| |
| # NOTE: The sub parsers don't work well with typing hence `type: ignore`. |
| # See https://github.com/python/typeshed/issues/10082. |
| sub_parser = subparsers.add_parser( # type: ignore |
| 'sub_cmd', help='Sub command.', inherited_absl_flags=self._absl_flags |
| ) |
| sub_parser.add_argument('--sub_flag', help='Sub command flag.') |
| |
| def sub_command_func(): |
| pass |
| |
| sub_parser.set_defaults(command=sub_command_func) |
| |
| args = parser.parse_args([ |
| '--header=HEADER', '--absl_string=new_value', 'sub_cmd', |
| '--absl_integer=2', '--sub_flag=new_sub_flag_value']) |
| |
| self.assertEqual(args.header, 'HEADER') |
| self.assertEqual(self._absl_flags.absl_string, 'new_value') |
| self.assertEqual(args.command, sub_command_func) |
| self.assertEqual(self._absl_flags.absl_integer, 2) |
| self.assertEqual(args.sub_flag, 'new_sub_flag_value') |
| |
| def test_subparsers_no_inherit_in_subparser(self): |
| parser = argparse_flags.ArgumentParser( |
| inherited_absl_flags=self._absl_flags) |
| subparsers = parser.add_subparsers(help='The command to execute.') |
| |
| # NOTE: The sub parsers don't work well with typing hence `type: ignore`. |
| # See https://github.com/python/typeshed/issues/10082. |
| subparsers.add_parser( # type: ignore |
| 'sub_cmd', |
| help='Sub command.', |
| # Do not inherit absl flags in the subparser. |
| # This is the behavior that this test exercises. |
| inherited_absl_flags=None, |
| ) |
| |
| with self.assertRaises(SystemExit): |
| parser.parse_args(['sub_cmd', '--absl_string=new_value']) |
| |
| def test_help_main_module_flags(self): |
| parser = argparse_flags.ArgumentParser( |
| inherited_absl_flags=self._absl_flags) |
| help_message = parser.format_help() |
| |
| # Only the short name is shown in the usage string. |
| self.assertIn('[-s ABSL_STRING]', help_message) |
| # Both names are included in the options section. |
| self.assertIn('-s ABSL_STRING, --absl_string ABSL_STRING', help_message) |
| # Verify help messages. |
| self.assertIn('help for --absl_string=%.', help_message) |
| self.assertIn('<apple|orange>: help for --absl_enum.', help_message) |
| |
| def test_help_non_main_module_flags(self): |
| flags.DEFINE_string( |
| 'non_main_module_flag', 'default', 'help', |
| module_name='other.module', flag_values=self._absl_flags) |
| parser = argparse_flags.ArgumentParser( |
| inherited_absl_flags=self._absl_flags) |
| help_message = parser.format_help() |
| |
| # Non main module key flags are not printed in the help message. |
| self.assertNotIn('non_main_module_flag', help_message) |
| |
| def test_help_non_main_module_key_flags(self): |
| flags.DEFINE_string( |
| 'non_main_module_flag', 'default', 'help', |
| module_name='other.module', flag_values=self._absl_flags) |
| flags.declare_key_flag('non_main_module_flag', flag_values=self._absl_flags) |
| parser = argparse_flags.ArgumentParser( |
| inherited_absl_flags=self._absl_flags) |
| help_message = parser.format_help() |
| |
| # Main module key fags are printed in the help message, even if the flag |
| # is defined in another module. |
| self.assertIn('non_main_module_flag', help_message) |
| |
| @parameterized.named_parameters( |
| ('h', ['-h']), |
| ('help', ['--help']), |
| ('helpshort', ['--helpshort']), |
| ('helpfull', ['--helpfull']), |
| ) |
| def test_help_flags(self, args): |
| parser = argparse_flags.ArgumentParser( |
| inherited_absl_flags=self._absl_flags) |
| with self.assertRaises(SystemExit): |
| parser.parse_args(args) |
| |
| @parameterized.named_parameters( |
| ('h', ['-h']), |
| ('help', ['--help']), |
| ('helpshort', ['--helpshort']), |
| ('helpfull', ['--helpfull']), |
| ) |
| def test_no_help_flags(self, args): |
| parser = argparse_flags.ArgumentParser( |
| inherited_absl_flags=self._absl_flags, add_help=False) |
| with mock.patch.object(parser, 'print_help') as print_help_mock: |
| with self.assertRaises(SystemExit): |
| parser.parse_args(args) |
| print_help_mock.assert_not_called() |
| |
| def test_helpfull_message(self): |
| flags.DEFINE_string( |
| 'non_main_module_flag', 'default', 'help', |
| module_name='other.module', flag_values=self._absl_flags) |
| parser = argparse_flags.ArgumentParser( |
| inherited_absl_flags=self._absl_flags) |
| with self.assertRaises(SystemExit),\ |
| mock.patch.object(sys, 'stdout', new=io.StringIO()) as mock_stdout: |
| parser.parse_args(['--helpfull']) |
| stdout_message = mock_stdout.getvalue() |
| logging.info('captured stdout message:\n%s', stdout_message) |
| self.assertIn('--non_main_module_flag', stdout_message) |
| self.assertIn('other.module', stdout_message) |
| # Make sure the main module is not included. |
| self.assertNotIn(sys.argv[0], stdout_message) |
| # Special flags defined in absl.flags. |
| self.assertIn('absl.flags:', stdout_message) |
| self.assertIn('--flagfile', stdout_message) |
| self.assertIn('--undefok', stdout_message) |
| |
| @parameterized.named_parameters( |
| ('at_end', |
| ('1', '--absl_string=value_from_cmd', '--flagfile='), |
| 'value_from_file'), |
| ('at_beginning', |
| ('--flagfile=', '1', '--absl_string=value_from_cmd'), |
| 'value_from_cmd'), |
| ) |
| def test_flagfile(self, cmd_args, expected_absl_string_value): |
| # Set gnu_getopt to False, to verify it's ignored by argparse_flags. |
| self._absl_flags.set_gnu_getopt(False) |
| |
| parser = argparse_flags.ArgumentParser( |
| inherited_absl_flags=self._absl_flags) |
| parser.add_argument('--header', help='Header message to print.') |
| parser.add_argument('integers', metavar='N', type=int, nargs='+', |
| help='an integer for the accumulator') |
| flagfile = tempfile.NamedTemporaryFile( |
| dir=absltest.TEST_TMPDIR.value, delete=False) |
| self.addCleanup(os.unlink, flagfile.name) |
| with flagfile: |
| flagfile.write(b''' |
| # The flag file. |
| --absl_string=value_from_file |
| --absl_integer=1 |
| --header=header_from_file |
| ''') |
| |
| expand_flagfile = lambda x: x + flagfile.name if x == '--flagfile=' else x |
| cmd_args = [expand_flagfile(x) for x in cmd_args] |
| args = parser.parse_args(cmd_args) |
| |
| self.assertEqual([1], args.integers) |
| self.assertEqual('header_from_file', args.header) |
| self.assertEqual(expected_absl_string_value, self._absl_flags.absl_string) |
| |
| @parameterized.parameters( |
| ('positional', {'positional'}, False), |
| ('--not_existed', {'existed'}, False), |
| ('--empty', set(), False), |
| ('-single_dash', {'single_dash'}, True), |
| ('--double_dash', {'double_dash'}, True), |
| ('--with_value=value', {'with_value'}, True), |
| ) |
| def test_is_undefok(self, arg, undefok_names, is_undefok): |
| self.assertEqual(is_undefok, argparse_flags._is_undefok(arg, undefok_names)) |
| |
| @parameterized.named_parameters( |
| ('single', 'single', ['--single'], []), |
| ('multiple', 'first,second', ['--first', '--second'], []), |
| ('single_dash', 'dash', ['-dash'], []), |
| ('mixed_dash', 'mixed', ['-mixed', '--mixed'], []), |
| ('value', 'name', ['--name=value'], []), |
| ('boolean_positive', 'bool', ['--bool'], []), |
| ('boolean_negative', 'bool', ['--nobool'], []), |
| ('left_over', 'strip', ['--first', '--strip', '--last'], |
| ['--first', '--last']), |
| ) |
| def test_strip_undefok_args(self, undefok, args, expected_args): |
| actual_args = argparse_flags._strip_undefok_args(undefok, args) |
| self.assertListEqual(expected_args, actual_args) |
| |
| @parameterized.named_parameters( |
| ('at_end', ['--unknown', '--undefok=unknown']), |
| ('at_beginning', ['--undefok=unknown', '--unknown']), |
| ('multiple', ['--unknown', '--undefok=unknown,another_unknown']), |
| ('with_value', ['--unknown=value', '--undefok=unknown']), |
| ('maybe_boolean', ['--nounknown', '--undefok=unknown']), |
| ('with_space', ['--unknown', '--undefok', 'unknown']), |
| ) |
| def test_undefok_flag_correct_use(self, cmd_args): |
| parser = argparse_flags.ArgumentParser( |
| inherited_absl_flags=self._absl_flags) |
| args = parser.parse_args(cmd_args) # Make sure it doesn't raise. |
| # Make sure `undefok` is not exposed in namespace. |
| sentinel = object() |
| self.assertIs(sentinel, getattr(args, 'undefok', sentinel)) |
| |
| def test_undefok_flag_existing(self): |
| parser = argparse_flags.ArgumentParser( |
| inherited_absl_flags=self._absl_flags) |
| parser.parse_args( |
| ['--absl_string=new_value', '--undefok=absl_string']) |
| self.assertEqual('new_value', self._absl_flags.absl_string) |
| |
| @parameterized.named_parameters( |
| ('no_equal', ['--unknown', 'value', '--undefok=unknown']), |
| ('single_dash', ['--unknown', '-undefok=unknown']), |
| ) |
| def test_undefok_flag_incorrect_use(self, cmd_args): |
| parser = argparse_flags.ArgumentParser( |
| inherited_absl_flags=self._absl_flags) |
| with self.assertRaises(SystemExit): |
| parser.parse_args(cmd_args) |
| |
| def test_argument_default(self): |
| # Regression test for https://github.com/abseil/abseil-py/issues/171. |
| parser = argparse_flags.ArgumentParser( |
| inherited_absl_flags=self._absl_flags, argument_default=23) |
| parser.add_argument( |
| '--magic_number', type=int, help='The magic number to use.') |
| args = parser.parse_args([]) |
| self.assertEqual(args.magic_number, 23) |
| |
| def test_empty_inherited_absl_flags(self): |
| parser = argparse_flags.ArgumentParser( |
| inherited_absl_flags=flags.FlagValues() |
| ) |
| parser.add_argument('--foo') |
| flagfile = self.create_tempfile(content='--foo=bar').full_path |
| # Make sure these flags are still available when inheriting an empty |
| # FlagValues instance. |
| ns = parser.parse_args([ |
| '--undefok=undefined_flag', |
| '--undefined_flag=value', |
| '--flagfile=' + flagfile, |
| ]) |
| self.assertEqual(ns.foo, 'bar') |
| |
| |
| class ArgparseWithAppRunTest(parameterized.TestCase): |
| |
| @parameterized.named_parameters( |
| ('simple', |
| 'main_simple', 'parse_flags_simple', |
| ['--argparse_echo=I am argparse.', '--absl_echo=I am absl.'], |
| ['I am argparse.', 'I am absl.']), |
| ('subcommand_roll_dice', |
| 'main_subcommands', 'parse_flags_subcommands', |
| ['--argparse_echo=I am argparse.', '--absl_echo=I am absl.', |
| 'roll_dice', '--num_faces=12'], |
| ['I am argparse.', 'I am absl.', 'Rolled a dice: ']), |
| ('subcommand_shuffle', |
| 'main_subcommands', 'parse_flags_subcommands', |
| ['--argparse_echo=I am argparse.', '--absl_echo=I am absl.', |
| 'shuffle', 'a', 'b', 'c'], |
| ['I am argparse.', 'I am absl.', 'Shuffled: ']), |
| ) |
| def test_argparse_with_app_run( |
| self, main_func_name, flags_parser_func_name, args, output_strings): |
| env = os.environ.copy() |
| env['MAIN_FUNC'] = main_func_name |
| env['FLAGS_PARSER_FUNC'] = flags_parser_func_name |
| helper = _bazelize_command.get_executable_path( |
| 'absl/flags/tests/argparse_flags_test_helper') |
| try: |
| stdout = subprocess.check_output( |
| [helper] + args, env=env, universal_newlines=True) |
| except subprocess.CalledProcessError as e: |
| error_info = ('ERROR: argparse_helper failed\n' |
| 'Command: {}\n' |
| 'Exit code: {}\n' |
| '----- output -----\n{}' |
| '------------------') |
| error_info = error_info.format(e.cmd, e.returncode, |
| e.output + '\n' if e.output else '<empty>') |
| print(error_info, file=sys.stderr) |
| raise |
| |
| for output_string in output_strings: |
| self.assertIn(output_string, stdout) |
| |
| |
| if __name__ == '__main__': |
| absltest.main() |