# Copyright 2017 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 flags.FlagValues class."""

import collections
import copy
import pickle
import types
from unittest import mock

from absl import logging
from absl.flags import _defines
from absl.flags import _exceptions
from absl.flags import _flagvalues
from absl.flags import _helpers
from absl.flags import _validators
from absl.flags.tests import module_foo
from absl.testing import absltest
from absl.testing import parameterized


class FlagValuesTest(absltest.TestCase):

  def test_bool_flags(self):
    for arg, expected in (('--nothing', True),
                          ('--nothing=true', True),
                          ('--nothing=false', False),
                          ('--nonothing', False)):
      fv = _flagvalues.FlagValues()
      _defines.DEFINE_boolean('nothing', None, '', flag_values=fv)
      fv(('./program', arg))
      self.assertIs(expected, fv.nothing)

    for arg in ('--nonothing=true', '--nonothing=false'):
      fv = _flagvalues.FlagValues()
      _defines.DEFINE_boolean('nothing', None, '', flag_values=fv)
      with self.assertRaises(ValueError):
        fv(('./program', arg))

  def test_boolean_flag_parser_gets_string_argument(self):
    for arg, expected in (('--nothing', 'true'),
                          ('--nothing=true', 'true'),
                          ('--nothing=false', 'false'),
                          ('--nonothing', 'false')):
      fv = _flagvalues.FlagValues()
      _defines.DEFINE_boolean('nothing', None, '', flag_values=fv)
      with mock.patch.object(fv['nothing'].parser, 'parse') as mock_parse:
        fv(('./program', arg))
        mock_parse.assert_called_once_with(expected)

  def test_unregistered_flags_are_cleaned_up(self):
    fv = _flagvalues.FlagValues()
    module, module_name = _helpers.get_calling_module_object_and_name()

    # Define first flag.
    _defines.DEFINE_integer('cores', 4, '', flag_values=fv, short_name='c')
    old_cores_flag = fv['cores']
    fv.register_key_flag_for_module(module_name, old_cores_flag)
    self.assertEqual(fv.flags_by_module_dict(),
                     {module_name: [old_cores_flag]})
    self.assertEqual(fv.flags_by_module_id_dict(),
                     {id(module): [old_cores_flag]})
    self.assertEqual(fv.key_flags_by_module_dict(),
                     {module_name: [old_cores_flag]})

    # Redefine the same flag.
    _defines.DEFINE_integer(
        'cores', 4, '', flag_values=fv, short_name='c', allow_override=True)
    new_cores_flag = fv['cores']
    self.assertNotEqual(old_cores_flag, new_cores_flag)
    self.assertEqual(fv.flags_by_module_dict(),
                     {module_name: [new_cores_flag]})
    self.assertEqual(fv.flags_by_module_id_dict(),
                     {id(module): [new_cores_flag]})
    # old_cores_flag is removed from key flags, and the new_cores_flag is
    # not automatically added because it must be registered explicitly.
    self.assertEqual(fv.key_flags_by_module_dict(), {module_name: []})

    # Define a new flag but with the same short_name.
    _defines.DEFINE_integer(
        'changelist',
        0,
        '',
        flag_values=fv,
        short_name='c',
        allow_override=True)
    old_changelist_flag = fv['changelist']
    fv.register_key_flag_for_module(module_name, old_changelist_flag)
    # The short named flag -c is overridden to be the old_changelist_flag.
    self.assertEqual(fv['c'], old_changelist_flag)
    self.assertNotEqual(fv['c'], new_cores_flag)
    self.assertEqual(fv.flags_by_module_dict(),
                     {module_name: [new_cores_flag, old_changelist_flag]})
    self.assertEqual(fv.flags_by_module_id_dict(),
                     {id(module): [new_cores_flag, old_changelist_flag]})
    self.assertEqual(fv.key_flags_by_module_dict(),
                     {module_name: [old_changelist_flag]})

    # Define a flag only with the same long name.
    _defines.DEFINE_integer(
        'changelist',
        0,
        '',
        flag_values=fv,
        short_name='l',
        allow_override=True)
    new_changelist_flag = fv['changelist']
    self.assertNotEqual(old_changelist_flag, new_changelist_flag)
    self.assertEqual(fv.flags_by_module_dict(),
                     {module_name: [new_cores_flag,
                                    old_changelist_flag,
                                    new_changelist_flag]})
    self.assertEqual(fv.flags_by_module_id_dict(),
                     {id(module): [new_cores_flag,
                                   old_changelist_flag,
                                   new_changelist_flag]})
    self.assertEqual(fv.key_flags_by_module_dict(),
                     {module_name: [old_changelist_flag]})

    # Delete the new changelist's long name, it should still be registered
    # because of its short name.
    del fv.changelist
    self.assertNotIn('changelist', fv)
    self.assertEqual(fv.flags_by_module_dict(),
                     {module_name: [new_cores_flag,
                                    old_changelist_flag,
                                    new_changelist_flag]})
    self.assertEqual(fv.flags_by_module_id_dict(),
                     {id(module): [new_cores_flag,
                                   old_changelist_flag,
                                   new_changelist_flag]})
    self.assertEqual(fv.key_flags_by_module_dict(),
                     {module_name: [old_changelist_flag]})

    # Delete the new changelist's short name, it should be removed.
    del fv.l
    self.assertNotIn('l', fv)
    self.assertEqual(fv.flags_by_module_dict(),
                     {module_name: [new_cores_flag,
                                    old_changelist_flag]})
    self.assertEqual(fv.flags_by_module_id_dict(),
                     {id(module): [new_cores_flag,
                                   old_changelist_flag]})
    self.assertEqual(fv.key_flags_by_module_dict(),
                     {module_name: [old_changelist_flag]})

  def _test_find_module_or_id_defining_flag(self, test_id):
    """Tests for find_module_defining_flag and find_module_id_defining_flag.

    Args:
      test_id: True to test find_module_id_defining_flag, False to test
          find_module_defining_flag.
    """
    fv = _flagvalues.FlagValues()
    current_module, current_module_name = (
        _helpers.get_calling_module_object_and_name())
    alt_module_name = _flagvalues.__name__

    if test_id:
      current_module_or_id = id(current_module)
      alt_module_or_id = id(_flagvalues)
      testing_fn = fv.find_module_id_defining_flag
    else:
      current_module_or_id = current_module_name
      alt_module_or_id = alt_module_name
      testing_fn = fv.find_module_defining_flag

    # Define first flag.
    _defines.DEFINE_integer('cores', 4, '', flag_values=fv, short_name='c')
    module_or_id_cores = testing_fn('cores')
    self.assertEqual(module_or_id_cores, current_module_or_id)
    module_or_id_c = testing_fn('c')
    self.assertEqual(module_or_id_c, current_module_or_id)

    # Redefine the same flag in another module.
    _defines.DEFINE_integer(
        'cores',
        4,
        '',
        flag_values=fv,
        module_name=alt_module_name,
        short_name='c',
        allow_override=True)
    module_or_id_cores = testing_fn('cores')
    self.assertEqual(module_or_id_cores, alt_module_or_id)
    module_or_id_c = testing_fn('c')
    self.assertEqual(module_or_id_c, alt_module_or_id)

    # Define a new flag but with the same short_name.
    _defines.DEFINE_integer(
        'changelist',
        0,
        '',
        flag_values=fv,
        short_name='c',
        allow_override=True)
    module_or_id_cores = testing_fn('cores')
    self.assertEqual(module_or_id_cores, alt_module_or_id)
    module_or_id_changelist = testing_fn('changelist')
    self.assertEqual(module_or_id_changelist, current_module_or_id)
    module_or_id_c = testing_fn('c')
    self.assertEqual(module_or_id_c, current_module_or_id)

    # Define a flag in another module only with the same long name.
    _defines.DEFINE_integer(
        'changelist',
        0,
        '',
        flag_values=fv,
        module_name=alt_module_name,
        short_name='l',
        allow_override=True)
    module_or_id_cores = testing_fn('cores')
    self.assertEqual(module_or_id_cores, alt_module_or_id)
    module_or_id_changelist = testing_fn('changelist')
    self.assertEqual(module_or_id_changelist, alt_module_or_id)
    module_or_id_c = testing_fn('c')
    self.assertEqual(module_or_id_c, current_module_or_id)
    module_or_id_l = testing_fn('l')
    self.assertEqual(module_or_id_l, alt_module_or_id)

    # Delete the changelist flag, its short name should still be registered.
    del fv.changelist
    module_or_id_changelist = testing_fn('changelist')
    self.assertIsNone(module_or_id_changelist)
    module_or_id_c = testing_fn('c')
    self.assertEqual(module_or_id_c, current_module_or_id)
    module_or_id_l = testing_fn('l')
    self.assertEqual(module_or_id_l, alt_module_or_id)

  def test_find_module_defining_flag(self):
    self._test_find_module_or_id_defining_flag(test_id=False)

  def test_find_module_id_defining_flag(self):
    self._test_find_module_or_id_defining_flag(test_id=True)

  def test_set_default(self):
    fv = _flagvalues.FlagValues()
    fv.mark_as_parsed()
    with self.assertRaises(_exceptions.UnrecognizedFlagError):
      fv.set_default('changelist', 1)
    _defines.DEFINE_integer('changelist', 0, 'help', flag_values=fv)
    self.assertEqual(0, fv.changelist)
    fv.set_default('changelist', 2)
    self.assertEqual(2, fv.changelist)

  def test_default_gnu_getopt_value(self):
    self.assertTrue(_flagvalues.FlagValues().is_gnu_getopt())

  def test_known_only_flags_in_gnustyle(self):

    def run_test(argv, defined_py_flags, expected_argv):
      fv = _flagvalues.FlagValues()
      fv.set_gnu_getopt(True)
      for f in defined_py_flags:
        if f.startswith('b'):
          _defines.DEFINE_boolean(f, False, 'help', flag_values=fv)
        else:
          _defines.DEFINE_string(f, 'default', 'help', flag_values=fv)
      output_argv = fv(argv, known_only=True)
      self.assertEqual(expected_argv, output_argv)

    run_test(
        argv='0 --f1=v1 cmd --f2 v2 --b1 --f3 v3 --nob2'.split(' '),
        defined_py_flags=[],
        expected_argv='0 --f1=v1 cmd --f2 v2 --b1 --f3 v3 --nob2'.split(' '))
    run_test(
        argv='0 --f1=v1 cmd --f2 v2 --b1 --f3 v3 --nob2'.split(' '),
        defined_py_flags=['f1'],
        expected_argv='0 cmd --f2 v2 --b1 --f3 v3 --nob2'.split(' '))
    run_test(
        argv='0 --f1=v1 cmd --f2 v2 --b1 --f3 v3 --nob2'.split(' '),
        defined_py_flags=['f2'],
        expected_argv='0 --f1=v1 cmd --b1 --f3 v3 --nob2'.split(' '))
    run_test(
        argv='0 --f1=v1 cmd --f2 v2 --b1 --f3 v3 --nob2'.split(' '),
        defined_py_flags=['b1'],
        expected_argv='0 --f1=v1 cmd --f2 v2 --f3 v3 --nob2'.split(' '))
    run_test(
        argv='0 --f1=v1 cmd --f2 v2 --b1 --f3 v3 --nob2'.split(' '),
        defined_py_flags=['f3'],
        expected_argv='0 --f1=v1 cmd --f2 v2 --b1 --nob2'.split(' '))
    run_test(
        argv='0 --f1=v1 cmd --f2 v2 --b1 --f3 v3 --nob2'.split(' '),
        defined_py_flags=['b2'],
        expected_argv='0 --f1=v1 cmd --f2 v2 --b1 --f3 v3'.split(' '))
    run_test(
        argv=('0 --f1=v1 cmd --undefok=f1 --f2 v2 --b1 '
              '--f3 v3 --nob2').split(' '),
        defined_py_flags=['b2'],
        expected_argv='0 cmd --f2 v2 --b1 --f3 v3'.split(' '))
    run_test(
        argv=('0 --f1=v1 cmd --undefok f1,f2 --f2 v2 --b1 '
              '--f3 v3 --nob2').split(' '),
        defined_py_flags=['b2'],
        # Note v2 is preserved here, since undefok requires the flag being
        # specified in the form of --flag=value.
        expected_argv='0 cmd v2 --b1 --f3 v3'.split(' '))

  def test_invalid_flag_name(self):
    with self.assertRaises(_exceptions.Error):
      _defines.DEFINE_boolean('test ', 0, '')

    with self.assertRaises(_exceptions.Error):
      _defines.DEFINE_boolean(' test', 0, '')

    with self.assertRaises(_exceptions.Error):
      _defines.DEFINE_boolean('te st', 0, '')

    with self.assertRaises(_exceptions.Error):
      _defines.DEFINE_boolean('', 0, '')

    with self.assertRaises(_exceptions.Error):
      _defines.DEFINE_boolean(1, 0, '')  # type: ignore

  def test_len(self):
    fv = _flagvalues.FlagValues()
    self.assertEmpty(fv)
    self.assertFalse(fv)

    _defines.DEFINE_boolean('boolean', False, 'help', flag_values=fv)
    self.assertLen(fv, 1)
    self.assertTrue(fv)

    _defines.DEFINE_boolean(
        'bool', False, 'help', short_name='b', flag_values=fv)
    self.assertLen(fv, 3)
    self.assertTrue(fv)

  def test_pickle(self):
    fv = _flagvalues.FlagValues()
    with self.assertRaisesRegex(TypeError, "can't pickle FlagValues"):
      pickle.dumps(fv)

  def test_copy(self):
    fv = _flagvalues.FlagValues()
    _defines.DEFINE_integer('answer', 0, 'help', flag_values=fv)
    fv(['', '--answer=1'])

    with self.assertRaisesRegex(TypeError,
                                'FlagValues does not support shallow copies'):
      copy.copy(fv)

    fv2 = copy.deepcopy(fv)
    self.assertEqual(fv2.answer, 1)

    fv2.answer = 42
    self.assertEqual(fv2.answer, 42)
    self.assertEqual(fv.answer, 1)

  def test_conflicting_flags(self):
    fv = _flagvalues.FlagValues()
    with self.assertRaises(_exceptions.FlagNameConflictsWithMethodError):
      _defines.DEFINE_boolean('is_gnu_getopt', False, 'help', flag_values=fv)
    _defines.DEFINE_boolean(
        'is_gnu_getopt',
        False,
        'help',
        flag_values=fv,
        allow_using_method_names=True)
    self.assertFalse(fv['is_gnu_getopt'].value)
    self.assertIsInstance(fv.is_gnu_getopt, types.MethodType)

  def test_get_flags_for_module(self):
    fv = _flagvalues.FlagValues()
    _defines.DEFINE_string('foo', None, 'help', flag_values=fv)
    module_foo.define_flags(fv)
    flags = fv.get_flags_for_module('__main__')

    self.assertEqual({'foo'}, {flag.name for flag in flags})

    flags = fv.get_flags_for_module(module_foo)
    self.assertEqual({'tmod_foo_bool', 'tmod_foo_int', 'tmod_foo_str'},
                     {flag.name for flag in flags})

  def test_get_help(self):
    fv = _flagvalues.FlagValues()
    self.assertMultiLineEqual('''\
--flagfile: Insert flag definitions from the given file into the command line.
  (default: '')
--undefok: comma-separated list of flag names that it is okay to specify on the
  command line even if the program does not define a flag with that name.
  IMPORTANT: flags in this list that have arguments MUST use the --flag=value
  format.
  (default: '')''', fv.get_help())

    module_foo.define_flags(fv)
    self.assertMultiLineEqual('''
absl.flags.tests.module_bar:
  --tmod_bar_t: Sample int flag.
    (default: '4')
    (an integer)
  --tmod_bar_u: Sample int flag.
    (default: '5')
    (an integer)
  --tmod_bar_v: Sample int flag.
    (default: '6')
    (an integer)
  --[no]tmod_bar_x: Boolean flag.
    (default: 'true')
  --tmod_bar_y: String flag.
    (default: 'default')
  --[no]tmod_bar_z: Another boolean flag from module bar.
    (default: 'false')

absl.flags.tests.module_foo:
  --[no]tmod_foo_bool: Boolean flag from module foo.
    (default: 'true')
  --tmod_foo_int: Sample int flag.
    (default: '3')
    (an integer)
  --tmod_foo_str: String flag.
    (default: 'default')

absl.flags:
  --flagfile: Insert flag definitions from the given file into the command line.
    (default: '')
  --undefok: comma-separated list of flag names that it is okay to specify on
    the command line even if the program does not define a flag with that name.
    IMPORTANT: flags in this list that have arguments MUST use the --flag=value
    format.
    (default: '')''', fv.get_help())

    self.assertMultiLineEqual('''
xxxxabsl.flags.tests.module_bar:
xxxx  --tmod_bar_t: Sample int flag.
xxxx    (default: '4')
xxxx    (an integer)
xxxx  --tmod_bar_u: Sample int flag.
xxxx    (default: '5')
xxxx    (an integer)
xxxx  --tmod_bar_v: Sample int flag.
xxxx    (default: '6')
xxxx    (an integer)
xxxx  --[no]tmod_bar_x: Boolean flag.
xxxx    (default: 'true')
xxxx  --tmod_bar_y: String flag.
xxxx    (default: 'default')
xxxx  --[no]tmod_bar_z: Another boolean flag from module bar.
xxxx    (default: 'false')

xxxxabsl.flags.tests.module_foo:
xxxx  --[no]tmod_foo_bool: Boolean flag from module foo.
xxxx    (default: 'true')
xxxx  --tmod_foo_int: Sample int flag.
xxxx    (default: '3')
xxxx    (an integer)
xxxx  --tmod_foo_str: String flag.
xxxx    (default: 'default')

xxxxabsl.flags:
xxxx  --flagfile: Insert flag definitions from the given file into the command
xxxx    line.
xxxx    (default: '')
xxxx  --undefok: comma-separated list of flag names that it is okay to specify
xxxx    on the command line even if the program does not define a flag with that
xxxx    name.  IMPORTANT: flags in this list that have arguments MUST use the
xxxx    --flag=value format.
xxxx    (default: '')''', fv.get_help(prefix='xxxx'))

    self.assertMultiLineEqual('''
absl.flags.tests.module_bar:
  --tmod_bar_t: Sample int flag.
    (default: '4')
    (an integer)
  --tmod_bar_u: Sample int flag.
    (default: '5')
    (an integer)
  --tmod_bar_v: Sample int flag.
    (default: '6')
    (an integer)
  --[no]tmod_bar_x: Boolean flag.
    (default: 'true')
  --tmod_bar_y: String flag.
    (default: 'default')
  --[no]tmod_bar_z: Another boolean flag from module bar.
    (default: 'false')

absl.flags.tests.module_foo:
  --[no]tmod_foo_bool: Boolean flag from module foo.
    (default: 'true')
  --tmod_foo_int: Sample int flag.
    (default: '3')
    (an integer)
  --tmod_foo_str: String flag.
    (default: 'default')''', fv.get_help(include_special_flags=False))

  def test_str(self):
    fv = _flagvalues.FlagValues()
    self.assertEqual(str(fv), fv.get_help())
    module_foo.define_flags(fv)
    self.assertEqual(str(fv), fv.get_help())

  def test_empty_argv(self):
    fv = _flagvalues.FlagValues()
    with self.assertRaises(ValueError):
      fv([])

  def test_invalid_argv(self):
    fv = _flagvalues.FlagValues()
    with self.assertRaises(TypeError):
      fv('./program')  # type: ignore
    with self.assertRaises(TypeError):
      fv(b'./program')  # type: ignore

  def test_flags_dir(self):
    flag_values = _flagvalues.FlagValues()
    flag_name1 = 'bool_flag'
    flag_name2 = 'string_flag'
    flag_name3 = 'float_flag'
    description = 'Description'
    _defines.DEFINE_boolean(
        flag_name1, None, description, flag_values=flag_values)
    _defines.DEFINE_string(
        flag_name2, None, description, flag_values=flag_values)
    self.assertEqual(sorted([flag_name1, flag_name2]), dir(flag_values))

    _defines.DEFINE_float(
        flag_name3, None, description, flag_values=flag_values)
    self.assertEqual(
        sorted([flag_name1, flag_name2, flag_name3]), dir(flag_values))

  def test_flags_into_string_deterministic(self):
    flag_values = _flagvalues.FlagValues()
    _defines.DEFINE_string(
        'fa', 'x', '', flag_values=flag_values, module_name='mb')
    _defines.DEFINE_string(
        'fb', 'x', '', flag_values=flag_values, module_name='mb')
    _defines.DEFINE_string(
        'fc', 'x', '', flag_values=flag_values, module_name='ma')
    _defines.DEFINE_string(
        'fd', 'x', '', flag_values=flag_values, module_name='ma')

    expected = ('--fc=x\n'
                '--fd=x\n'
                '--fa=x\n'
                '--fb=x\n')

    flags_by_module_items = sorted(
        flag_values.flags_by_module_dict().items(), reverse=True)
    for _, module_flags in flags_by_module_items:
      module_flags.sort(reverse=True)

    flag_values.__dict__['__flags_by_module'] = collections.OrderedDict(
        flags_by_module_items)

    actual = flag_values.flags_into_string()
    self.assertEqual(expected, actual)

  def test_validate_all_flags(self):
    fv = _flagvalues.FlagValues()
    _defines.DEFINE_string('name', None, '', flag_values=fv)
    _validators.mark_flag_as_required('name', flag_values=fv)
    with self.assertRaises(_exceptions.IllegalFlagValueError):
      fv.validate_all_flags()
    fv.name = 'test'
    fv.validate_all_flags()


class FlagValuesLoggingTest(absltest.TestCase):
  """Test to make sure logging.* functions won't recurse.

  Logging may and does happen before flags initialization. We need to make
  sure that any warnings trown by flagvalues do not result in unlimited
  recursion.
  """

  def test_logging_do_not_recurse(self):
    logging.info('test info')
    try:
      raise ValueError('test exception')
    except ValueError:
      logging.exception('test message')


class FlagSubstrMatchingTests(parameterized.TestCase):
  """Tests related to flag substring matching."""

  def _get_test_flag_values(self):
    """Get a _flagvalues.FlagValues() instance, set up for tests."""
    flag_values = _flagvalues.FlagValues()

    _defines.DEFINE_string('strf', '', '', flag_values=flag_values)
    _defines.DEFINE_boolean('boolf', 0, '', flag_values=flag_values)

    return flag_values

  # Test cases that should always make parsing raise an error.
  # Tuples of strings with the argv to use.
  FAIL_TEST_CASES = [
      ('./program', '--boo', '0'),
      ('./program', '--boo=true', '0'),
      ('./program', '--boo=0'),
      ('./program', '--noboo'),
      ('./program', '--st=blah'),
      ('./program', '--st=de'),
      ('./program', '--st=blah', '--boo'),
      ('./program', '--st=blah', 'unused'),
      ('./program', '--st=--blah'),
      ('./program', '--st', '--blah'),
  ]

  @parameterized.parameters(FAIL_TEST_CASES)
  def test_raise(self, *argv):
    """Test that raising works."""
    fv = self._get_test_flag_values()
    with self.assertRaises(_exceptions.UnrecognizedFlagError):
      fv(argv)

  @parameterized.parameters(
      FAIL_TEST_CASES + [('./program', 'unused', '--st=blah')])
  def test_gnu_getopt_raise(self, *argv):
    """Test that raising works when combined with GNU-style getopt."""
    fv = self._get_test_flag_values()
    fv.set_gnu_getopt()
    with self.assertRaises(_exceptions.UnrecognizedFlagError):
      fv(argv)


class SettingUnknownFlagTest(absltest.TestCase):

  def setUp(self):
    super(SettingUnknownFlagTest, self).setUp()
    self.setter_called = 0

  def set_undef(self, unused_name, unused_val):
    self.setter_called += 1

  def test_raise_on_undefined(self):
    new_flags = _flagvalues.FlagValues()
    with self.assertRaises(_exceptions.UnrecognizedFlagError):
      new_flags.undefined_flag = 0

  def test_not_raise(self):
    new_flags = _flagvalues.FlagValues()
    new_flags._register_unknown_flag_setter(self.set_undef)
    new_flags.undefined_flag = 0
    self.assertEqual(self.setter_called, 1)

  def test_not_raise_on_undefined_if_undefok(self):
    new_flags = _flagvalues.FlagValues()
    args = ['0', '--foo', '--bar=1', '--undefok=foo,bar']
    unparsed = new_flags(args, known_only=True)
    self.assertEqual(['0'], unparsed)

  def test_re_raise_undefined(self):
    def setter(unused_name, unused_val):
      raise NameError()
    new_flags = _flagvalues.FlagValues()
    new_flags._register_unknown_flag_setter(setter)
    with self.assertRaises(_exceptions.UnrecognizedFlagError):
      new_flags.undefined_flag = 0

  def test_re_raise_invalid(self):
    def setter(unused_name, unused_val):
      raise ValueError()
    new_flags = _flagvalues.FlagValues()
    new_flags._register_unknown_flag_setter(setter)
    with self.assertRaises(_exceptions.IllegalFlagValueError):
      new_flags.undefined_flag = 0


class SetAttributesTest(absltest.TestCase):

  def setUp(self):
    super(SetAttributesTest, self).setUp()
    self.new_flags = _flagvalues.FlagValues()
    _defines.DEFINE_boolean(
        'defined_flag', None, '', flag_values=self.new_flags)
    _defines.DEFINE_boolean(
        'another_defined_flag', None, '', flag_values=self.new_flags)
    self.setter_called = 0

  def set_undef(self, unused_name, unused_val):
    self.setter_called += 1

  def test_two_defined_flags(self):
    self.new_flags._set_attributes(
        defined_flag=False, another_defined_flag=False)
    self.assertEqual(self.setter_called, 0)

  def test_one_defined_one_undefined_flag(self):
    with self.assertRaises(_exceptions.UnrecognizedFlagError):
      self.new_flags._set_attributes(defined_flag=False, undefined_flag=0)

  def test_register_unknown_flag_setter(self):
    self.new_flags._register_unknown_flag_setter(self.set_undef)
    self.new_flags._set_attributes(defined_flag=False, undefined_flag=0)
    self.assertEqual(self.setter_called, 1)


class FlagsDashSyntaxTest(absltest.TestCase):

  def setUp(self):
    super(FlagsDashSyntaxTest, self).setUp()
    self.fv = _flagvalues.FlagValues()
    _defines.DEFINE_string(
        'long_name', 'default', 'help', flag_values=self.fv, short_name='s')

  def test_long_name_one_dash(self):
    self.fv(['./program', '-long_name=new'])
    self.assertEqual('new', self.fv.long_name)

  def test_long_name_two_dashes(self):
    self.fv(['./program', '--long_name=new'])
    self.assertEqual('new', self.fv.long_name)

  def test_long_name_three_dashes(self):
    with self.assertRaises(_exceptions.UnrecognizedFlagError):
      self.fv(['./program', '---long_name=new'])

  def test_short_name_one_dash(self):
    self.fv(['./program', '-s=new'])
    self.assertEqual('new', self.fv.s)

  def test_short_name_two_dashes(self):
    self.fv(['./program', '--s=new'])
    self.assertEqual('new', self.fv.s)

  def test_short_name_three_dashes(self):
    with self.assertRaises(_exceptions.UnrecognizedFlagError):
      self.fv(['./program', '---s=new'])


class UnparseFlagsTest(absltest.TestCase):

  def test_using_default_value_none(self):
    fv = _flagvalues.FlagValues()
    _defines.DEFINE_string('default_none', None, 'help', flag_values=fv)
    self.assertTrue(fv['default_none'].using_default_value)
    fv(['', '--default_none=notNone'])
    self.assertFalse(fv['default_none'].using_default_value)
    fv.unparse_flags()
    self.assertTrue(fv['default_none'].using_default_value)
    fv(['', '--default_none=alsoNotNone'])
    self.assertFalse(fv['default_none'].using_default_value)
    fv.unparse_flags()
    self.assertTrue(fv['default_none'].using_default_value)

  def test_using_default_value_not_none(self):
    fv = _flagvalues.FlagValues()
    _defines.DEFINE_string('default_foo', 'foo', 'help', flag_values=fv)

    fv.mark_as_parsed()
    self.assertTrue(fv['default_foo'].using_default_value)

    fv(['', '--default_foo=foo'])
    self.assertFalse(fv['default_foo'].using_default_value)

    fv(['', '--default_foo=notFoo'])
    self.assertFalse(fv['default_foo'].using_default_value)

    fv.unparse_flags()
    self.assertTrue(fv['default_foo'].using_default_value)

    fv(['', '--default_foo=alsoNotFoo'])
    self.assertFalse(fv['default_foo'].using_default_value)

  def test_allow_overwrite_false(self):
    fv = _flagvalues.FlagValues()
    _defines.DEFINE_string(
        'default_none', None, 'help', allow_overwrite=False, flag_values=fv)
    _defines.DEFINE_string(
        'default_foo', 'foo', 'help', allow_overwrite=False, flag_values=fv)

    fv.mark_as_parsed()
    self.assertEqual('foo', fv.default_foo)
    self.assertIsNone(fv.default_none)

    fv(['', '--default_foo=notFoo', '--default_none=notNone'])
    self.assertEqual('notFoo', fv.default_foo)
    self.assertEqual('notNone', fv.default_none)

    fv.unparse_flags()
    self.assertEqual('foo', fv['default_foo'].value)
    self.assertIsNone(fv['default_none'].value)

    fv(['', '--default_foo=alsoNotFoo', '--default_none=alsoNotNone'])
    self.assertEqual('alsoNotFoo', fv.default_foo)
    self.assertEqual('alsoNotNone', fv.default_none)

  def test_multi_string_default_none(self):
    fv = _flagvalues.FlagValues()
    _defines.DEFINE_multi_string('foo', None, 'help', flag_values=fv)
    fv.mark_as_parsed()
    self.assertIsNone(fv.foo)
    fv(['', '--foo=aa'])
    self.assertEqual(['aa'], fv.foo)
    fv.unparse_flags()
    self.assertIsNone(fv['foo'].value)
    fv(['', '--foo=bb', '--foo=cc'])
    self.assertEqual(['bb', 'cc'], fv.foo)
    fv.unparse_flags()
    self.assertIsNone(fv['foo'].value)

  def test_multi_string_default_string(self):
    fv = _flagvalues.FlagValues()
    _defines.DEFINE_multi_string('foo', 'xyz', 'help', flag_values=fv)
    expected_default = ['xyz']
    fv.mark_as_parsed()
    self.assertEqual(expected_default, fv.foo)
    fv(['', '--foo=aa'])
    self.assertEqual(['aa'], fv.foo)
    fv.unparse_flags()
    self.assertEqual(expected_default, fv['foo'].value)
    fv(['', '--foo=bb', '--foo=cc'])
    self.assertEqual(['bb', 'cc'], fv['foo'].value)
    fv.unparse_flags()
    self.assertEqual(expected_default, fv['foo'].value)

  def test_multi_string_default_list(self):
    fv = _flagvalues.FlagValues()
    _defines.DEFINE_multi_string(
        'foo', ['xx', 'yy', 'zz'], 'help', flag_values=fv)
    expected_default = ['xx', 'yy', 'zz']
    fv.mark_as_parsed()
    self.assertEqual(expected_default, fv.foo)
    fv(['', '--foo=aa'])
    self.assertEqual(['aa'], fv.foo)
    fv.unparse_flags()
    self.assertEqual(expected_default, fv['foo'].value)
    fv(['', '--foo=bb', '--foo=cc'])
    self.assertEqual(['bb', 'cc'], fv.foo)
    fv.unparse_flags()
    self.assertEqual(expected_default, fv['foo'].value)


class UnparsedFlagAccessTest(absltest.TestCase):

  def test_unparsed_flag_access(self):
    fv = _flagvalues.FlagValues()
    _defines.DEFINE_string('name', 'default', 'help', flag_values=fv)
    with self.assertRaises(_exceptions.UnparsedFlagAccessError):
      _ = fv.name

  def test_hasattr_raises_in_py3(self):
    fv = _flagvalues.FlagValues()
    _defines.DEFINE_string('name', 'default', 'help', flag_values=fv)
    with self.assertRaises(_exceptions.UnparsedFlagAccessError):
      _ = hasattr(fv, 'name')

  def test_unparsed_flags_access_raises_after_unparse_flags(self):
    fv = _flagvalues.FlagValues()
    _defines.DEFINE_string('a_str', 'default_value', 'help', flag_values=fv)
    fv.mark_as_parsed()
    self.assertEqual(fv.a_str, 'default_value')
    fv.unparse_flags()
    with self.assertRaises(_exceptions.UnparsedFlagAccessError):
      _ = fv.a_str


class FlagHolderTest(absltest.TestCase):

  def setUp(self):
    super(FlagHolderTest, self).setUp()
    self.fv = _flagvalues.FlagValues()
    self.name_flag = _defines.DEFINE_string(
        'name', 'default', 'help', flag_values=self.fv)

  def parse_flags(self, *argv):
    self.fv.unparse_flags()
    self.fv(['binary_name'] + list(argv))

  def test_name(self):
    self.assertEqual('name', self.name_flag.name)

  def test_value_before_flag_parsing(self):
    with self.assertRaises(_exceptions.UnparsedFlagAccessError):
      _ = self.name_flag.value

  def test_value_returns_default_value_if_not_explicitly_set(self):
    self.parse_flags()
    self.assertEqual('default', self.name_flag.value)

  def test_value_returns_explicitly_set_value(self):
    self.parse_flags('--name=new_value')
    self.assertEqual('new_value', self.name_flag.value)

  def test_present_returns_false_before_flag_parsing(self):
    self.assertFalse(self.name_flag.present)

  def test_present_returns_false_if_not_explicitly_set(self):
    self.parse_flags()
    self.assertFalse(self.name_flag.present)

  def test_present_returns_true_if_explicitly_set(self):
    self.parse_flags('--name=new_value')
    self.assertTrue(self.name_flag.present)

  def test_serializes_flag(self):
    self.parse_flags('--name=new_value')
    self.assertEqual('--name=new_value', self.name_flag.serialize())

  def test_allow_override(self):
    first = _defines.DEFINE_integer(
        'int_flag', 1, 'help', flag_values=self.fv, allow_override=1)
    second = _defines.DEFINE_integer(
        'int_flag', 2, 'help', flag_values=self.fv, allow_override=1)
    self.parse_flags('--int_flag=3')
    self.assertEqual(3, first.value)
    self.assertEqual(3, second.value)
    self.assertTrue(first.present)
    self.assertTrue(second.present)

  def test_eq(self):
    with self.assertRaises(TypeError):
      self.name_flag == 'value'  # pylint: disable=pointless-statement

  def test_eq_reflection(self):
    with self.assertRaises(TypeError):
      'value' == self.name_flag  # pylint: disable=pointless-statement

  def test_bool(self):
    with self.assertRaises(TypeError):
      bool(self.name_flag)


if __name__ == '__main__':
  absltest.main()
