Skip to content

Commit f30627d

Browse files
committed
Added tests
1 parent 486734e commit f30627d

File tree

4 files changed

+131
-11
lines changed

4 files changed

+131
-11
lines changed

cmd2/cmd2.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
List,
5757
Mapping,
5858
Optional,
59+
Set,
5960
Tuple,
6061
Type,
6162
Union,
@@ -226,7 +227,7 @@ def __init__(
226227
terminators: Optional[List[str]] = None,
227228
shortcuts: Optional[Dict[str, str]] = None,
228229
command_sets: Optional[Iterable[CommandSet]] = None,
229-
auto_load_commands: bool = True
230+
auto_load_commands: bool = True,
230231
) -> None:
231232
"""An easy but powerful framework for writing line-oriented command
232233
interpreters. Extends Python's cmd package.
@@ -310,12 +311,13 @@ def __init__(
310311
# A dictionary mapping settable names to their Settable instance
311312
self._settables: Dict[str, Settable] = dict()
312313
self.always_prefix_settables: bool = False
313-
self.build_settables()
314314

315315
# CommandSet containers
316-
self._installed_command_sets: List[CommandSet] = []
316+
self._installed_command_sets: Set[CommandSet] = set()
317317
self._cmd_to_command_sets: Dict[str, CommandSet] = {}
318318

319+
self.build_settables()
320+
319321
# Use as prompt for multiline commands on the 2nd+ line of input
320322
self.continuation_prompt = '> '
321323

@@ -622,7 +624,7 @@ def register_command_set(self, cmdset: CommandSet) -> None:
622624
if default_category and not hasattr(method, constants.CMD_ATTR_HELP_CATEGORY):
623625
utils.categorize(method, default_category)
624626

625-
self._installed_command_sets.append(cmdset)
627+
self._installed_command_sets.add(cmdset)
626628

627629
self._register_subcommands(cmdset)
628630
cmdset.on_registered()
@@ -918,6 +920,9 @@ def add_settable(self, settable: Settable) -> None:
918920
919921
:param settable: Settable object being added
920922
"""
923+
if not self.always_prefix_settables:
924+
if settable.name in self.settables.keys() and settable.name not in self._settables.keys():
925+
raise KeyError(f'Duplicate settable: {settable.name}')
921926
if settable.settable_obj is None:
922927
settable.settable_obj = self
923928
self._settables[settable.name] = settable
@@ -1310,7 +1315,7 @@ def flag_based_complete(
13101315
endidx: int,
13111316
flag_dict: Dict[str, Union[Iterable, Callable]],
13121317
*,
1313-
all_else: Union[None, Iterable, Callable] = None
1318+
all_else: Union[None, Iterable, Callable] = None,
13141319
) -> List[str]:
13151320
"""Tab completes based on a particular flag preceding the token being completed.
13161321
@@ -1359,7 +1364,7 @@ def index_based_complete(
13591364
endidx: int,
13601365
index_dict: Mapping[int, Union[Iterable, Callable]],
13611366
*,
1362-
all_else: Union[None, Iterable, Callable] = None
1367+
all_else: Union[None, Iterable, Callable] = None,
13631368
) -> List[str]:
13641369
"""Tab completes based on a fixed position in the input string.
13651370
@@ -2561,7 +2566,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState:
25612566
stdout=subprocess.PIPE if isinstance(self.stdout, utils.StdSim) else self.stdout,
25622567
stderr=subprocess.PIPE if isinstance(sys.stderr, utils.StdSim) else sys.stderr,
25632568
shell=True,
2564-
**kwargs
2569+
**kwargs,
25652570
)
25662571

25672572
# Popen was called with shell=True so the user can chain pipe commands and redirect their output
@@ -2735,7 +2740,7 @@ def read_input(
27352740
choices: Iterable = None,
27362741
choices_provider: Optional[Callable] = None,
27372742
completer: Optional[Callable] = None,
2738-
parser: Optional[argparse.ArgumentParser] = None
2743+
parser: Optional[argparse.ArgumentParser] = None,
27392744
) -> str:
27402745
"""
27412746
Read input from appropriate stdin value. Also supports tab completion and up-arrow history while

setup.cfg

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
[tool:pytest]
22
testpaths =
33
tests
4+
addopts =
5+
--cov=cmd2
6+
--cov-append
7+
--cov-report=term
8+
--cov-report=html
49

510
[flake8]
611
count = True
@@ -37,3 +42,16 @@ use_parentheses = true
3742
ignore-path=docs/_build,.git,.idea,.pytest_cache,.tox,.nox,.venv,.vscode,build,cmd2,examples,tests,cmd2.egg-info,dist,htmlcov,__pycache__,*.egg,plugins
3843
max-line-length=120
3944
verbose=0
45+
46+
[mypy]
47+
disallow_incomplete_defs = True
48+
disallow_untyped_defs = True
49+
disallow_untyped_calls = True
50+
warn_redundant_casts = True
51+
warn_unused_ignores = False
52+
warn_return_any = True
53+
warn_unreachable = True
54+
strict = True
55+
show_error_context = True
56+
show_column_numbers = True
57+
show_error_codes = True

tests/test_cmd2.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,8 @@ def onchange_app():
229229

230230
def test_set_onchange_hook(onchange_app):
231231
out, err = run_cmd(onchange_app, 'set quiet True')
232-
expected = normalize("""
232+
expected = normalize(
233+
"""
233234
You changed quiet
234235
quiet - was: False
235236
now: True

tests_isolated/test_commandset/test_commandset.py

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,17 @@
1313

1414
import cmd2
1515
from cmd2 import (
16-
utils,
16+
Settable,
1717
)
1818
from cmd2.exceptions import (
1919
CommandSetRegistrationError,
2020
)
2121

2222
from .conftest import (
23-
WithCommandSets,
2423
complete_tester,
24+
normalize,
25+
run_cmd,
26+
WithCommandSets,
2527
)
2628

2729

@@ -910,3 +912,97 @@ def cut_banana(self, ns: argparse.Namespace):
910912

911913
with pytest.raises(CommandSetRegistrationError):
912914
app = BadSubcommandApp()
915+
916+
917+
def test_commandset_settables():
918+
# Define an arbitrary class with some attribute
919+
class Arbitrary:
920+
def __init__(self):
921+
self.some_value = 5
922+
923+
# Declare a CommandSet with a settable of some arbitrary property
924+
class WithSettablesA(CommandSetBase):
925+
def __init__(self):
926+
super(WithSettablesA, self).__init__()
927+
928+
self._arbitrary = Arbitrary()
929+
self._settable_prefix = 'addon'
930+
931+
self.add_settable(
932+
Settable(
933+
'arbitrary_value',
934+
int,
935+
'Some settable value',
936+
settable_object=self._arbitrary,
937+
settable_attrib_name='some_value',
938+
)
939+
)
940+
941+
# create the command set and cmd2
942+
cmdset = WithSettablesA()
943+
arbitrary2 = Arbitrary()
944+
app = cmd2.Cmd(command_sets=[cmdset])
945+
app.add_settable(Settable('always_prefix_settables', bool, 'Prefix settables'))
946+
947+
assert 'arbitrary_value' in app.settables.keys()
948+
assert 'always_prefix_settables' in app.settables.keys()
949+
950+
# verify the settable shows up
951+
out, err = run_cmd(app, 'set')
952+
assert 'arbitrary_value: 5' in out
953+
out, err = run_cmd(app, 'set arbitrary_value')
954+
assert out == ['arbitrary_value: 5']
955+
956+
# change the value and verify the value changed
957+
out, err = run_cmd(app, 'set arbitrary_value 10')
958+
expected = """
959+
arbitrary_value - was: 5
960+
now: 10
961+
"""
962+
assert out == normalize(expected)
963+
out, err = run_cmd(app, 'set arbitrary_value')
964+
assert out == ['arbitrary_value: 10']
965+
966+
# can't add to cmd2 now because commandset already has this settable
967+
with pytest.raises(KeyError):
968+
app.add_settable(Settable('arbitrary_value', int, 'This should fail'))
969+
970+
cmdset.add_settable(
971+
Settable('arbitrary_value', int, 'Replaced settable', settable_object=arbitrary2, settable_attrib_name='some_value')
972+
)
973+
974+
# Can't add a settable to the commandset that already exists in cmd2
975+
with pytest.raises(KeyError):
976+
cmdset.add_settable(Settable('always_prefix_settables', int, 'This should also fail'))
977+
978+
# Can't remove a settable from the CommandSet if it is elsewhere and not in the CommandSet
979+
with pytest.raises(KeyError):
980+
cmdset.remove_settable('always_prefix_settables')
981+
982+
# unregister the CommandSet and verify the settable is now gone
983+
app.unregister_command_set(cmdset)
984+
out, err = run_cmd(app, 'set')
985+
assert 'arbitrary_value' not in out
986+
out, err = run_cmd(app, 'set arbitrary_value')
987+
expected = """
988+
Parameter 'arbitrary_value' not supported (type 'set' for list of parameters).
989+
"""
990+
assert err == normalize(expected)
991+
992+
# turn on prefixes and add the commandset back
993+
app.always_prefix_settables = True
994+
app.register_command_set(cmdset)
995+
996+
# Verify the settable is back with the defined prefix.
997+
998+
assert 'addon.arbitrary_value' in app.settables.keys()
999+
1000+
# rename the prefix and verify that the prefix changes everywhere
1001+
cmdset._settable_prefix = 'some'
1002+
assert 'addon.arbitrary_value' not in app.settables.keys()
1003+
assert 'some.arbitrary_value' in app.settables.keys()
1004+
1005+
out, err = run_cmd(app, 'set')
1006+
assert 'some.arbitrary_value: 5' in out
1007+
out, err = run_cmd(app, 'set some.arbitrary_value')
1008+
assert out == ['some.arbitrary_value: 5']

0 commit comments

Comments
 (0)