BIND 10 trac2254, updated. 5c7990cd039e4350cf2823e8edc10b509d6d8ba7 [2254] added more tests
BIND 10 source code commits
bind10-changes at lists.isc.org
Tue Sep 25 22:31:37 UTC 2012
The branch, trac2254 has been updated
via 5c7990cd039e4350cf2823e8edc10b509d6d8ba7 (commit)
via 0b9f5f6d4afd45a3482ca0d4aec22f5d00df1865 (commit)
via 6978e7fbdfb9c036e1f0fedf04828d6edc731d9e (commit)
from 9b8b55c00ba7bba8e4f95336eb2bb34c0b151efb (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 5c7990cd039e4350cf2823e8edc10b509d6d8ba7
Author: Jelte Jansen <jelte at isc.org>
Date: Wed Sep 26 00:31:28 2012 +0200
[2254] added more tests
commit 0b9f5f6d4afd45a3482ca0d4aec22f5d00df1865
Author: Jelte Jansen <jelte at isc.org>
Date: Tue Sep 25 23:59:40 2012 +0200
[2254] change exception type
and add comment to tests
commit 6978e7fbdfb9c036e1f0fedf04828d6edc731d9e
Author: Jelte Jansen <jelte at isc.org>
Date: Tue Sep 25 23:14:22 2012 +0200
[2254] refactor tab-completion for identifiers
removed all word-boundary values except whitespace (so that we no longer have to to magic to replace things around the / character, and other possible characters used in identifiers)
Pulled out the identifier-hints collection (and added tests for those)
-----------------------------------------------------------------------
Summary of changes:
src/bin/bindctl/bindcmd.py | 70 +++++++++-----------
src/bin/bindctl/bindctl_main.py.in | 14 ++--
src/bin/bindctl/tests/bindctl_test.py | 34 ++++++++++
src/lib/python/isc/config/ccsession.py | 6 +-
src/lib/python/isc/config/config_data.py | 12 ++--
src/lib/python/isc/config/tests/ccsession_test.py | 16 +++--
.../python/isc/config/tests/config_data_test.py | 22 ++++++
7 files changed, 113 insertions(+), 61 deletions(-)
-----------------------------------------------------------------------
diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py
index 87aa217..9e7b1e1 100644
--- a/src/bin/bindctl/bindcmd.py
+++ b/src/bin/bindctl/bindcmd.py
@@ -48,20 +48,18 @@ except ImportError:
# if we have readline support, use that, otherwise use normal stdio
try:
import readline
- # This is a fix for the problem described in
- # http://bind10.isc.org/ticket/1345
- # If '-' is seen as a word-boundary, the final completion-step
- # (as handled by the cmd module, and hence outside our reach) can
- # mistakenly add data twice, resulting in wrong completion results
- # The solution is to remove it.
- delims = readline.get_completer_delims()
- delims = delims.replace('-', '')
- readline.set_completer_delims(delims)
+ # Only consider whitespace as word boundaries
+ readline.set_completer_delims(' \t\n')
my_readline = readline.get_line_buffer
except ImportError:
my_readline = sys.stdin.readline
+# Used for tab-completion of 'identifiers' (i.e. config values)
+# If a command parameter has this name, the tab completion hints
+# are derived from config data
+IDENTIFIER_PARAM = 'identifier'
+
CSV_FILE_NAME = 'default_user.csv'
CONFIG_MODULE_NAME = 'config'
CONST_BINDCTL_HELP = """
@@ -463,20 +461,24 @@ class BindCmdInterpreter(Cmd):
Cmd.onecmd(self, line)
- def remove_prefix(self, list, prefix):
- """Removes the prefix already entered, and all elements from the
- list that don't match it"""
- if prefix.startswith('/'):
- prefix = prefix[1:]
-
- new_list = []
- for val in list:
- if val.startswith(prefix):
- new_val = val[len(prefix):]
- if new_val.startswith("/"):
- new_val = new_val[1:]
- new_list.append(new_val)
- return new_list
+ def _get_identifier_startswith(self, id_text):
+ """Return the tab-completion hints for identifiers starting with
+ id_text"""
+ # First get all items from the given module (up to the first /)
+ list = self.config_data.get_config_item_list(
+ id_text.rpartition("/")[0], True)
+ hints = [val for val in list if val.startswith(id_text[1:])]
+ return hints
+
+ def _cmd_has_identifier_param(self, cmd):
+ """
+ Returns True if the given (parsed) command is known and has a
+ parameter which points to a config data identifier
+ """
+ if cmd.module not in self.modules:
+ return False
+ command = self.modules[cmd.module].get_command_with_name(cmd.command)
+ return command.has_param_with_name(IDENTIFIER_PARAM)
def complete(self, text, state):
if 0 == state:
@@ -491,17 +493,12 @@ class BindCmdInterpreter(Cmd):
else:
hints = self._get_param_startswith(cmd.module, cmd.command,
text)
- if cmd.module == CONFIG_MODULE_NAME:
- # grm text has been stripped of slashes...
- my_text = self.location + "/" + cur_line.rpartition(" ")[2]
- list = self.config_data.get_config_item_list(my_text.rpartition("/")[0], True)
- hints.extend([val for val in list if val.startswith(my_text[1:])])
- # remove the common prefix from the hints so we don't get it twice
- prefix, _, rest = my_text.rpartition("/")
- hints = self.remove_prefix(hints, prefix)
- # And prevent 'double addition' by also removing final
- # part matches
- hints = [ h for h in hints if h != rest ]
+ if self._cmd_has_identifier_param(cmd):
+ # For tab-completion of identifiers, replace hardcoded
+ # hints with hints derived from the config data
+ id_text = self.location + "/" + cur_line.rpartition(" ")[2]
+ hints = self._get_identifier_startswith(id_text)
+
except CmdModuleNameFormatError:
if not text:
hints = self.get_module_names()
@@ -522,13 +519,8 @@ class BindCmdInterpreter(Cmd):
except BindCtlException:
hints = []
- # there are a couple of 'standard' names that are usable, but
- # should not be included in direct tab-completion
- hints = [ h for h in hints if h not in [ 'argument', 'identifier']]
-
self.hint = hints
-
if state < len(self.hint):
return self.hint[state]
else:
diff --git a/src/bin/bindctl/bindctl_main.py.in b/src/bin/bindctl/bindctl_main.py.in
index 1685355..b6a5b18 100755
--- a/src/bin/bindctl/bindctl_main.py.in
+++ b/src/bin/bindctl/bindctl_main.py.in
@@ -42,12 +42,12 @@ def prepare_config_commands(tool):
cmd = CommandInfo(name = "show", desc = "Show configuration.")
param = ParamInfo(name = "argument", type = "string", optional=True, desc = "If you specify the argument 'all' (before the identifier), recursively show all child elements for the given identifier.")
cmd.add_param(param)
- param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
+ param = ParamInfo(name = IDENTIFIER_PARAM, type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
cmd.add_param(param)
module.add_command(cmd)
cmd = CommandInfo(name = "show_json", desc = "Show full configuration in JSON format.")
- param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
+ param = ParamInfo(name = IDENTIFIER_PARAM, type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
cmd.add_param(param)
module.add_command(cmd)
@@ -60,7 +60,7 @@ def prepare_config_commands(tool):
"parameter value, similar to when adding to a list. "
"In either case, when no value is given, an entry will be "
"constructed with default values.")
- param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
+ param = ParamInfo(name = IDENTIFIER_PARAM, type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
cmd.add_param(param)
param = ParamInfo(name = "value_or_name", type = "string", optional=True, desc = "Specifies a value to add to the list, or the name when adding to a named set. It must be in correct JSON format and complete.")
cmd.add_param(param)
@@ -70,21 +70,21 @@ def prepare_config_commands(tool):
module.add_command(cmd)
cmd = CommandInfo(name = "remove", desc = "Remove entry from configuration list or named set.")
- param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
+ param = ParamInfo(name = IDENTIFIER_PARAM, type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
cmd.add_param(param)
param = ParamInfo(name = "value", type = "string", optional=True, desc = "When identifier is a list, specifies a value to remove from the list. It must be in correct JSON format and complete. When it is a named set, specifies the name to remove.")
cmd.add_param(param)
module.add_command(cmd)
cmd = CommandInfo(name = "set", desc = "Set a configuration value.")
- param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
+ param = ParamInfo(name = IDENTIFIER_PARAM, type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
cmd.add_param(param)
param = ParamInfo(name = "value", type = "string", optional=False, desc = "Specifies a value to set. It must be in correct JSON format and complete.")
cmd.add_param(param)
module.add_command(cmd)
cmd = CommandInfo(name = "unset", desc = "Unset a configuration value (i.e. revert to the default, if any).")
- param = ParamInfo(name = "identifier", type = "string", optional=False, desc = DEFAULT_IDENTIFIER_DESC)
+ param = ParamInfo(name = IDENTIFIER_PARAM, type = "string", optional=False, desc = DEFAULT_IDENTIFIER_DESC)
cmd.add_param(param)
module.add_command(cmd)
@@ -98,7 +98,7 @@ def prepare_config_commands(tool):
module.add_command(cmd)
cmd = CommandInfo(name = "go", desc = "Go to a specific configuration part.")
- param = ParamInfo(name = "identifier", type="string", optional=False, desc = DEFAULT_IDENTIFIER_DESC)
+ param = ParamInfo(name = IDENTIFIER_PARAM, type="string", optional=False, desc = DEFAULT_IDENTIFIER_DESC)
cmd.add_param(param)
module.add_command(cmd)
diff --git a/src/bin/bindctl/tests/bindctl_test.py b/src/bin/bindctl/tests/bindctl_test.py
index bcfb6c5..3364211 100644
--- a/src/bin/bindctl/tests/bindctl_test.py
+++ b/src/bin/bindctl/tests/bindctl_test.py
@@ -419,6 +419,40 @@ class TestConfigCommands(unittest.TestCase):
def tearDown(self):
sys.stdout = self.stdout_backup
+ def test_cmd_has_identifier_param(self):
+ module = ModuleInfo(name = "test_module")
+
+ cmd = CommandInfo(name = "command_with_identifier")
+ param = ParamInfo(name = bindcmd.IDENTIFIER_PARAM)
+ cmd.add_param(param)
+ module.add_command(cmd)
+
+ cmd = CommandInfo(name = "command_without_identifier")
+ param = ParamInfo(name = "some_argument")
+ cmd.add_param(param)
+ module.add_command(cmd)
+
+ self.tool.add_module_info(module)
+
+ cmd = cmdparse.BindCmdParse("test_module command_with_identifier")
+ self.assertTrue(self.tool._cmd_has_identifier_param(cmd))
+
+ cmd = cmdparse.BindCmdParse("test_module command_without_identifier")
+ self.assertFalse(self.tool._cmd_has_identifier_param(cmd))
+
+ cmd = cmdparse.BindCmdParse("badmodule command_without_identifier")
+ self.assertFalse(self.tool._cmd_has_identifier_param(cmd))
+
+ def test_get_identifier_startswith(self):
+ hints = self.tool._get_identifier_startswith("/")
+ self.assertEqual([ 'foo/an_int', 'foo/a_list'], hints)
+
+ hints = self.tool._get_identifier_startswith("/foo/an")
+ self.assertEqual(['foo/an_int'], hints)
+
+ hints = self.tool._get_identifier_startswith("/bar")
+ self.assertEqual([], hints)
+
class FakeBindCmdInterpreter(bindcmd.BindCmdInterpreter):
def __init__(self):
pass
diff --git a/src/lib/python/isc/config/ccsession.py b/src/lib/python/isc/config/ccsession.py
index 1a45bf3..8464a01 100644
--- a/src/lib/python/isc/config/ccsession.py
+++ b/src/lib/python/isc/config/ccsession.py
@@ -575,7 +575,7 @@ class UIModuleCCSession(MultiConfigData):
# for type any, we determine the 'type' by what value is set
# (which would be either list or dict)
cur_value, _ = self.get_value(identifier)
- type_any = module_spec['item_type'] == 'any'
+ type_any = isc.config.config_data.spec_part_is_any(module_spec)
# the specified element must be a list or a named_set
if 'list_item_spec' in module_spec or\
@@ -603,7 +603,7 @@ class UIModuleCCSession(MultiConfigData):
self._add_value_to_named_set(identifier, item_name,
item_value)
else:
- raise isc.cc.data.DataNotFoundError(str(identifier) + " is not a list or a named set")
+ raise isc.cc.data.DataTypeError(str(identifier) + " is not a list or a named set")
def _remove_value_from_list(self, identifier, value):
if value is None:
@@ -668,7 +668,7 @@ class UIModuleCCSession(MultiConfigData):
(type_any and type(cur_value) == dict):
self._remove_value_from_named_set(identifier, value_str)
else:
- raise isc.cc.data.DataNotFoundError(str(identifier) + " is not a list or a named_set")
+ raise isc.cc.data.DataTypeError(str(identifier) + " is not a list or a named_set")
diff --git a/src/lib/python/isc/config/config_data.py b/src/lib/python/isc/config/config_data.py
index e09ce33..654bdd7 100644
--- a/src/lib/python/isc/config/config_data.py
+++ b/src/lib/python/isc/config/config_data.py
@@ -802,18 +802,18 @@ class MultiConfigData:
the list will only contain the item_name itself."""
spec_part = self.find_spec_part(item_name)
if spec_part_is_named_set(spec_part):
- subslash = ""
- if spec_part['named_set_item_spec']['item_type'] == 'map' or\
- spec_part['named_set_item_spec']['item_type'] == 'named_set':
- subslash = "/"
values, status = self.get_value(item_name)
- if len(values) > 0:
+ if values is not None and len(values) > 0:
+ subslash = ""
+ if spec_part['named_set_item_spec']['item_type'] == 'map' or\
+ spec_part['named_set_item_spec']['item_type'] == 'named_set':
+ subslash = "/"
return [ item_name + "/" + v + subslash for v in values.keys() ]
else:
return [ item_name ]
elif spec_part_is_list(spec_part):
values, status = self.get_value(item_name)
- if len(values) > 0:
+ if values is not None and len(values) > 0:
result = []
for i in range(len(values)):
name = item_name + '[%d]' % i
diff --git a/src/lib/python/isc/config/tests/ccsession_test.py b/src/lib/python/isc/config/tests/ccsession_test.py
index f81b1db..ad364ac 100644
--- a/src/lib/python/isc/config/tests/ccsession_test.py
+++ b/src/lib/python/isc/config/tests/ccsession_test.py
@@ -993,11 +993,15 @@ class TestUIModuleCCSession(unittest.TestCase):
self.assertRaises(isc.cc.data.DataNotFoundError, uccs.add_value, 1, "a")
self.assertRaises(isc.cc.data.DataNotFoundError, uccs.add_value, "no_such_item", "a")
- self.assertRaises(isc.cc.data.DataNotFoundError, uccs.add_value, "Spec2/item1", "a")
self.assertRaises(isc.cc.data.DataNotFoundError, uccs.remove_value, 1, "a")
self.assertRaises(isc.cc.data.DataNotFoundError, uccs.remove_value, "no_such_item", "a")
- self.assertRaises(isc.cc.data.DataNotFoundError, uccs.remove_value, "Spec2/item1", "a")
- self.assertRaises(isc.cc.data.DataNotFoundError, uccs.remove_value, "Spec2", "")
+ # add and remove should raise DataNotFoundError when used with items
+ # that are not a list or named_set (more importantly, they should
+ # not raise TypeError)
+ self.assertRaises(isc.cc.data.DataTypeError, uccs.add_value, "Spec2/item1", "a")
+ self.assertRaises(isc.cc.data.DataTypeError, uccs.remove_value, "Spec2/item1", "a")
+ self.assertRaises(isc.cc.data.DataTypeError, uccs.add_value, "Spec2", "")
+ self.assertRaises(isc.cc.data.DataTypeError, uccs.remove_value, "Spec2", "")
self.assertEqual({}, uccs._local_changes)
uccs.add_value("Spec2/item5", "foo")
@@ -1044,9 +1048,9 @@ class TestUIModuleCCSession(unittest.TestCase):
items_as_str = [ '1234', 'foo', 'true', 'false' ]
def test_fails():
- self.assertRaises(isc.cc.data.DataNotFoundError, uccs.add_value, "Spec40/item1", "foo")
- self.assertRaises(isc.cc.data.DataNotFoundError, uccs.add_value, "Spec40/item1", "foo", "bar")
- self.assertRaises(isc.cc.data.DataNotFoundError, uccs.remove_value, "Spec40/item1", "foo")
+ self.assertRaises(isc.cc.data.DataTypeError, uccs.add_value, "Spec40/item1", "foo")
+ self.assertRaises(isc.cc.data.DataTypeError, uccs.add_value, "Spec40/item1", "foo", "bar")
+ self.assertRaises(isc.cc.data.DataTypeError, uccs.remove_value, "Spec40/item1", "foo")
self.assertRaises(isc.cc.data.DataTypeError, uccs.remove_value, "Spec40/item1[0]", None)
# A few helper functions to perform a number of tests
diff --git a/src/lib/python/isc/config/tests/config_data_test.py b/src/lib/python/isc/config/tests/config_data_test.py
index 4375811..6da46cb 100644
--- a/src/lib/python/isc/config/tests/config_data_test.py
+++ b/src/lib/python/isc/config/tests/config_data_test.py
@@ -252,6 +252,18 @@ class TestConfigData(unittest.TestCase):
self.assertRaises(ConfigDataError, spec_name_list, 1)
self.assertRaises(ConfigDataError, spec_name_list, [ 'a' ])
+ # Test one with type any as well
+ module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec40.spec")
+ spec_part = module_spec.get_config_spec()
+ name_list = spec_name_list(module_spec.get_config_spec())
+ self.assertEqual(['item1', 'item2', 'item3'], name_list)
+
+ # item3 itself is 'empty'
+ spec_part = find_spec_part(spec_part, 'item3')
+ name_list = spec_name_list(spec_part)
+ self.assertEqual([], name_list)
+
+
def test_init(self):
self.assertRaises(ConfigDataError, ConfigData, "asdf")
@@ -739,6 +751,11 @@ class TestMultiConfigData(unittest.TestCase):
config_items = self.mcd.get_config_item_list("Spec2", True)
self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5[0]', 'Spec2/item5[1]', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
+ # When lists are empty, it should only show the name
+ self.mcd.set_value('Spec2/item5', [])
+ config_items = self.mcd.get_config_item_list("Spec2", True)
+ self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
+
def test_is_named_set(self):
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec32.spec")
self.mcd.set_specification(module_spec)
@@ -766,6 +783,11 @@ class TestMultiConfigData(unittest.TestCase):
'Spec32/named_set_item/bbbb',
], config_items)
+ self.mcd.set_value('Spec32/named_set_item', {})
+ config_items = self.mcd.get_config_item_list("/Spec32/named_set_item",
+ True)
+ self.assertEqual(['Spec32/named_set_item'], config_items)
+
def test_set_named_set_nonlocal(self):
# Test whether a default named set is copied to local if a subitem
# is changed, and that other items in the set do not get lost
More information about the bind10-changes
mailing list